Mirrowel commited on
Commit
2d9a112
Β·
1 Parent(s): 24a4b19

style(ui): πŸ’Ž reduce spacing and font sizes for compact model filter layout

Browse files

Systematically reduced padding, margins, font sizes, and component dimensions throughout the ModelFilterGUI to create a more compact, space-efficient interface.

- Reduced title font from FONT_SIZE_NORMAL to FONT_SIZE_SMALL in RulePanel
- Decreased padding values (12β†’10, 8β†’6, etc.) across all UI components
- Reduced input entry and button heights from 36px to 28px, and button from 26px to smaller sizes
- Set minimum height constraint (70px) on rule list to prevent collapse
- Implemented grid-based responsive layout with proportional row weights (3:1 ratio for models vs rules)
- Prevented input frame height changes using pack_propagate(False)
- Refactored UI creation into _create_main_layout() with grid system replacing individual pack() calls
- Updated all components to use content_frame.grid() instead of self.pack() for better layout control

The changes maintain all functionality while significantly improving vertical space utilization, allowing more content to be visible without scrolling.

Files changed (1) hide show
  1. src/proxy_app/model_filter_gui.py +98 -74
src/proxy_app/model_filter_gui.py CHANGED
@@ -2191,14 +2191,14 @@ class RulePanel(ctk.CTkFrame):
2191
 
2192
  def _create_content(self):
2193
  """Build panel content."""
2194
- # Title
2195
  title_label = ctk.CTkLabel(
2196
  self,
2197
  text=self.title,
2198
- font=(FONT_FAMILY, FONT_SIZE_NORMAL, "bold"),
2199
  text_color=TEXT_PRIMARY,
2200
  )
2201
- title_label.pack(anchor="w", padx=12, pady=(12, 8))
2202
 
2203
  # Virtual rule list (replaces CTkScrollableFrame + RuleChips)
2204
  self.rule_list = VirtualRuleList(
@@ -2207,24 +2207,28 @@ class RulePanel(ctk.CTkFrame):
2207
  on_rule_click=self.on_rule_clicked,
2208
  on_rule_delete=self._on_rule_delete,
2209
  )
2210
- self.rule_list.pack(fill="both", expand=True, padx=8, pady=(0, 8))
 
 
 
2211
 
2212
- # Input frame
2213
- input_frame = ctk.CTkFrame(self, fg_color="transparent")
2214
- input_frame.pack(fill="x", padx=8, pady=(0, 8))
 
2215
 
2216
  # Pattern input
2217
  self.input_entry = ctk.CTkEntry(
2218
  input_frame,
2219
  placeholder_text="pattern1, pattern2*, ...",
2220
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2221
  fg_color=BG_TERTIARY,
2222
  border_color=BORDER_COLOR,
2223
  text_color=TEXT_PRIMARY,
2224
  placeholder_text_color=TEXT_MUTED,
2225
- height=36,
2226
  )
2227
- self.input_entry.pack(side="left", fill="x", expand=True, padx=(0, 8))
2228
  self.input_entry.bind("<Return>", self._on_add_clicked)
2229
  self.input_entry.bind("<KeyRelease>", self._on_input_key)
2230
 
@@ -2232,11 +2236,11 @@ class RulePanel(ctk.CTkFrame):
2232
  add_btn = ctk.CTkButton(
2233
  input_frame,
2234
  text="+ Add",
2235
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2236
  fg_color=ACCENT_BLUE,
2237
  hover_color="#3a8aee",
2238
- width=70,
2239
- height=36,
2240
  command=self._on_add_clicked,
2241
  )
2242
  add_btn.pack(side="right")
@@ -2342,13 +2346,8 @@ class ModelFilterGUI(ctk.CTk):
2342
  self._fetch_in_progress: bool = False
2343
  self._preview_after_id: Optional[str] = None
2344
 
2345
- # Build UI
2346
- self._create_header()
2347
- self._create_search_bar()
2348
- self._create_model_lists()
2349
- self._create_rule_panels()
2350
- self._create_status_bar()
2351
- self._create_action_buttons()
2352
 
2353
  # Context menu
2354
  self._create_context_menu()
@@ -2365,6 +2364,30 @@ class ModelFilterGUI(ctk.CTk):
2365
  # Focus and raise window after it's fully loaded
2366
  self.after(100, self._activate_window)
2367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2368
  def _activate_window(self):
2369
  """Activate and focus the window."""
2370
  self.lift()
@@ -2373,69 +2396,69 @@ class ModelFilterGUI(ctk.CTk):
2373
  self.after(200, lambda: self.attributes("-topmost", False))
2374
 
2375
  def _create_header(self):
2376
- """Create the header with provider selector and buttons."""
2377
- header = ctk.CTkFrame(self, fg_color="transparent")
2378
- header.pack(fill="x", padx=20, pady=(15, 10))
2379
 
2380
- # Title
2381
  title = ctk.CTkLabel(
2382
  header,
2383
  text="🎯 Model Filter Configuration",
2384
- font=(FONT_FAMILY, FONT_SIZE_HEADER, "bold"),
2385
  text_color=TEXT_PRIMARY,
2386
  )
2387
  title.pack(side="left")
2388
 
2389
- # Help button
2390
  help_btn = ctk.CTkButton(
2391
  header,
2392
  text="?",
2393
- font=(FONT_FAMILY, FONT_SIZE_LARGE, "bold"),
2394
  fg_color=BG_SECONDARY,
2395
  hover_color=BG_HOVER,
2396
  border_width=1,
2397
  border_color=BORDER_COLOR,
2398
- width=36,
2399
- height=36,
2400
- corner_radius=18,
2401
  command=self._show_help,
2402
  )
2403
- help_btn.pack(side="right", padx=(10, 0))
2404
  ToolTip(help_btn, "Help (F1)")
2405
 
2406
- # Refresh button
2407
  refresh_btn = ctk.CTkButton(
2408
  header,
2409
  text="πŸ”„ Refresh",
2410
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2411
  fg_color=BG_SECONDARY,
2412
  hover_color=BG_HOVER,
2413
  border_width=1,
2414
  border_color=BORDER_COLOR,
2415
- width=100,
2416
- height=36,
2417
  command=self._refresh_models,
2418
  )
2419
- refresh_btn.pack(side="right", padx=(10, 0))
2420
  ToolTip(refresh_btn, "Refresh models (Ctrl+R)")
2421
 
2422
- # Provider selector
2423
  provider_frame = ctk.CTkFrame(header, fg_color="transparent")
2424
  provider_frame.pack(side="right")
2425
 
2426
  provider_label = ctk.CTkLabel(
2427
  provider_frame,
2428
  text="Provider:",
2429
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2430
  text_color=TEXT_SECONDARY,
2431
  )
2432
- provider_label.pack(side="left", padx=(0, 8))
2433
 
2434
  self.provider_dropdown = ctk.CTkComboBox(
2435
  provider_frame,
2436
  values=["Loading..."],
2437
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2438
- dropdown_font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2439
  fg_color=BG_SECONDARY,
2440
  border_color=BORDER_COLOR,
2441
  button_color=BORDER_COLOR,
@@ -2443,25 +2466,25 @@ class ModelFilterGUI(ctk.CTk):
2443
  dropdown_fg_color=BG_SECONDARY,
2444
  dropdown_hover_color=BG_HOVER,
2445
  text_color=TEXT_PRIMARY,
2446
- width=180,
2447
- height=36,
2448
  state="readonly",
2449
  command=self._on_provider_changed,
2450
  )
2451
  self.provider_dropdown.pack(side="left")
2452
 
2453
  def _create_search_bar(self):
2454
- """Create the search bar."""
2455
- search_frame = ctk.CTkFrame(self, fg_color="transparent")
2456
- search_frame.pack(fill="x", padx=20, pady=(0, 10))
2457
 
2458
  search_icon = ctk.CTkLabel(
2459
  search_frame,
2460
  text="πŸ”",
2461
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2462
  text_color=TEXT_MUTED,
2463
  )
2464
- search_icon.pack(side="left", padx=(0, 8))
2465
 
2466
  self.search_entry = ctk.CTkEntry(
2467
  search_frame,
@@ -2471,7 +2494,7 @@ class ModelFilterGUI(ctk.CTk):
2471
  border_color=BORDER_COLOR,
2472
  text_color=TEXT_PRIMARY,
2473
  placeholder_text_color=TEXT_MUTED,
2474
- height=36,
2475
  )
2476
  self.search_entry.pack(side="left", fill="x", expand=True)
2477
  self.search_entry.bind("<KeyRelease>", self._on_search_changed)
@@ -2480,12 +2503,12 @@ class ModelFilterGUI(ctk.CTk):
2480
  clear_btn = ctk.CTkButton(
2481
  search_frame,
2482
  text="Γ—",
2483
- font=(FONT_FAMILY, FONT_SIZE_LARGE),
2484
  fg_color="transparent",
2485
  hover_color=BG_HOVER,
2486
  text_color=TEXT_MUTED,
2487
- width=36,
2488
- height=36,
2489
  command=self._clear_search,
2490
  )
2491
  clear_btn.pack(side="left")
@@ -2494,22 +2517,23 @@ class ModelFilterGUI(ctk.CTk):
2494
  """Create the synchronized model list panel."""
2495
  # Use the virtual list implementation for performance
2496
  self.model_list_panel = VirtualSyncModelLists(
2497
- self,
2498
  on_model_click=self._on_model_clicked,
2499
  on_model_right_click=self._on_model_right_clicked,
2500
  )
2501
- self.model_list_panel.pack(fill="both", expand=True, padx=20, pady=(0, 10))
2502
 
2503
  def _create_rule_panels(self):
2504
  """Create the ignore and whitelist rule panels."""
2505
- rules_frame = ctk.CTkFrame(self, fg_color="transparent")
2506
- rules_frame.pack(fill="x", padx=20, pady=(0, 10))
2507
- rules_frame.grid_columnconfigure(0, weight=1)
2508
- rules_frame.grid_columnconfigure(1, weight=1)
 
2509
 
2510
  # Ignore panel
2511
  self.ignore_panel = RulePanel(
2512
- rules_frame,
2513
  title="🚫 Ignore Rules",
2514
  rule_type="ignore",
2515
  on_rules_changed=self._on_rules_changed,
@@ -2522,7 +2546,7 @@ class ModelFilterGUI(ctk.CTk):
2522
 
2523
  # Whitelist panel
2524
  self.whitelist_panel = RulePanel(
2525
- rules_frame,
2526
  title="βœ“ Whitelist Rules",
2527
  rule_type="whitelist",
2528
  on_rules_changed=self._on_rules_changed,
@@ -2534,16 +2558,16 @@ class ModelFilterGUI(ctk.CTk):
2534
  self.whitelist_panel.set_delete_callback(self._remove_whitelist_pattern)
2535
 
2536
  def _create_status_bar(self):
2537
- """Create the status bar showing available count and action buttons."""
2538
  # Combined status bar and action buttons in one row
2539
- self.status_frame = ctk.CTkFrame(self, fg_color="transparent")
2540
- self.status_frame.pack(fill="x", padx=20, pady=(5, 15))
2541
 
2542
- # Status label (left side)
2543
  self.status_label = ctk.CTkLabel(
2544
  self.status_frame,
2545
  text="Select a provider to begin",
2546
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2547
  text_color=TEXT_SECONDARY,
2548
  )
2549
  self.status_label.pack(side="left")
@@ -2555,33 +2579,33 @@ class ModelFilterGUI(ctk.CTk):
2555
  font=(FONT_FAMILY, FONT_SIZE_SMALL),
2556
  text_color=ACCENT_YELLOW,
2557
  )
2558
- self.unsaved_label.pack(side="left", padx=(15, 0))
2559
 
2560
- # Buttons (right side)
2561
  # Discard button
2562
  discard_btn = ctk.CTkButton(
2563
  self.status_frame,
2564
  text="↩️ Discard",
2565
- font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2566
  fg_color=BG_SECONDARY,
2567
  hover_color=BG_HOVER,
2568
  border_width=1,
2569
  border_color=BORDER_COLOR,
2570
- width=110,
2571
- height=36,
2572
  command=self._discard_changes,
2573
  )
2574
- discard_btn.pack(side="right", padx=(10, 0))
2575
 
2576
  # Save button
2577
  save_btn = ctk.CTkButton(
2578
  self.status_frame,
2579
  text="πŸ’Ύ Save",
2580
- font=(FONT_FAMILY, FONT_SIZE_NORMAL, "bold"),
2581
  fg_color=ACCENT_GREEN,
2582
  hover_color="#27ae60",
2583
- width=110,
2584
- height=36,
2585
  command=self._save_changes,
2586
  )
2587
  save_btn.pack(side="right")
 
2191
 
2192
  def _create_content(self):
2193
  """Build panel content."""
2194
+ # Title (compact)
2195
  title_label = ctk.CTkLabel(
2196
  self,
2197
  text=self.title,
2198
+ font=(FONT_FAMILY, FONT_SIZE_SMALL, "bold"),
2199
  text_color=TEXT_PRIMARY,
2200
  )
2201
+ title_label.pack(anchor="w", padx=10, pady=(6, 3))
2202
 
2203
  # Virtual rule list (replaces CTkScrollableFrame + RuleChips)
2204
  self.rule_list = VirtualRuleList(
 
2207
  on_rule_click=self.on_rule_clicked,
2208
  on_rule_delete=self._on_rule_delete,
2209
  )
2210
+ self.rule_list.pack(fill="both", expand=True, padx=6, pady=(0, 3))
2211
+
2212
+ # Set minimum height for rule list to ensure it's visible
2213
+ self.rule_list.frame.configure(height=70)
2214
 
2215
+ # Input frame with fixed height (won't squish on resize)
2216
+ input_frame = ctk.CTkFrame(self, fg_color="transparent", height=32)
2217
+ input_frame.pack(fill="x", padx=6, pady=(0, 5))
2218
+ input_frame.pack_propagate(False) # Prevent children from changing frame height
2219
 
2220
  # Pattern input
2221
  self.input_entry = ctk.CTkEntry(
2222
  input_frame,
2223
  placeholder_text="pattern1, pattern2*, ...",
2224
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2225
  fg_color=BG_TERTIARY,
2226
  border_color=BORDER_COLOR,
2227
  text_color=TEXT_PRIMARY,
2228
  placeholder_text_color=TEXT_MUTED,
2229
+ height=28,
2230
  )
2231
+ self.input_entry.pack(side="left", fill="x", expand=True, padx=(0, 6))
2232
  self.input_entry.bind("<Return>", self._on_add_clicked)
2233
  self.input_entry.bind("<KeyRelease>", self._on_input_key)
2234
 
 
2236
  add_btn = ctk.CTkButton(
2237
  input_frame,
2238
  text="+ Add",
2239
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2240
  fg_color=ACCENT_BLUE,
2241
  hover_color="#3a8aee",
2242
+ width=55,
2243
+ height=28,
2244
  command=self._on_add_clicked,
2245
  )
2246
  add_btn.pack(side="right")
 
2346
  self._fetch_in_progress: bool = False
2347
  self._preview_after_id: Optional[str] = None
2348
 
2349
+ # Build UI with grid layout for responsive sizing
2350
+ self._create_main_layout()
 
 
 
 
 
2351
 
2352
  # Context menu
2353
  self._create_context_menu()
 
2364
  # Focus and raise window after it's fully loaded
2365
  self.after(100, self._activate_window)
2366
 
2367
+ def _create_main_layout(self):
2368
+ """Create the main layout with grid for responsive sizing."""
2369
+ # Main content frame using grid layout
2370
+ # This allows proportional sizing between model lists and rule panels
2371
+ self.content_frame = ctk.CTkFrame(self, fg_color="transparent")
2372
+ self.content_frame.pack(fill="both", expand=True, padx=20, pady=(8, 10))
2373
+
2374
+ # Configure grid weights for responsive layout
2375
+ # Using 3:1 ratio so models get significantly more space than rules
2376
+ self.content_frame.grid_columnconfigure(0, weight=1)
2377
+ self.content_frame.grid_rowconfigure(0, weight=0) # Header - fixed
2378
+ self.content_frame.grid_rowconfigure(1, weight=0) # Search - fixed
2379
+ self.content_frame.grid_rowconfigure(2, weight=3) # Model lists - expands most
2380
+ self.content_frame.grid_rowconfigure(3, weight=1) # Rule panels - expands less
2381
+ self.content_frame.grid_rowconfigure(4, weight=0) # Status bar - fixed
2382
+
2383
+ # Create all sections
2384
+ self._create_header()
2385
+ self._create_search_bar()
2386
+ self._create_model_lists()
2387
+ self._create_rule_panels()
2388
+ self._create_status_bar()
2389
+ self._create_action_buttons()
2390
+
2391
  def _activate_window(self):
2392
  """Activate and focus the window."""
2393
  self.lift()
 
2396
  self.after(200, lambda: self.attributes("-topmost", False))
2397
 
2398
  def _create_header(self):
2399
+ """Create the header with provider selector and buttons (compact)."""
2400
+ header = ctk.CTkFrame(self.content_frame, fg_color="transparent")
2401
+ header.grid(row=0, column=0, sticky="ew", pady=(0, 4))
2402
 
2403
+ # Title (smaller font)
2404
  title = ctk.CTkLabel(
2405
  header,
2406
  text="🎯 Model Filter Configuration",
2407
+ font=(FONT_FAMILY, FONT_SIZE_LARGE, "bold"),
2408
  text_color=TEXT_PRIMARY,
2409
  )
2410
  title.pack(side="left")
2411
 
2412
+ # Help button (smaller)
2413
  help_btn = ctk.CTkButton(
2414
  header,
2415
  text="?",
2416
+ font=(FONT_FAMILY, FONT_SIZE_NORMAL, "bold"),
2417
  fg_color=BG_SECONDARY,
2418
  hover_color=BG_HOVER,
2419
  border_width=1,
2420
  border_color=BORDER_COLOR,
2421
+ width=26,
2422
+ height=26,
2423
+ corner_radius=13,
2424
  command=self._show_help,
2425
  )
2426
+ help_btn.pack(side="right", padx=(8, 0))
2427
  ToolTip(help_btn, "Help (F1)")
2428
 
2429
+ # Refresh button (smaller)
2430
  refresh_btn = ctk.CTkButton(
2431
  header,
2432
  text="πŸ”„ Refresh",
2433
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2434
  fg_color=BG_SECONDARY,
2435
  hover_color=BG_HOVER,
2436
  border_width=1,
2437
  border_color=BORDER_COLOR,
2438
+ width=80,
2439
+ height=26,
2440
  command=self._refresh_models,
2441
  )
2442
+ refresh_btn.pack(side="right", padx=(8, 0))
2443
  ToolTip(refresh_btn, "Refresh models (Ctrl+R)")
2444
 
2445
+ # Provider selector (compact)
2446
  provider_frame = ctk.CTkFrame(header, fg_color="transparent")
2447
  provider_frame.pack(side="right")
2448
 
2449
  provider_label = ctk.CTkLabel(
2450
  provider_frame,
2451
  text="Provider:",
2452
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2453
  text_color=TEXT_SECONDARY,
2454
  )
2455
+ provider_label.pack(side="left", padx=(0, 6))
2456
 
2457
  self.provider_dropdown = ctk.CTkComboBox(
2458
  provider_frame,
2459
  values=["Loading..."],
2460
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2461
+ dropdown_font=(FONT_FAMILY, FONT_SIZE_SMALL),
2462
  fg_color=BG_SECONDARY,
2463
  border_color=BORDER_COLOR,
2464
  button_color=BORDER_COLOR,
 
2466
  dropdown_fg_color=BG_SECONDARY,
2467
  dropdown_hover_color=BG_HOVER,
2468
  text_color=TEXT_PRIMARY,
2469
+ width=160,
2470
+ height=26,
2471
  state="readonly",
2472
  command=self._on_provider_changed,
2473
  )
2474
  self.provider_dropdown.pack(side="left")
2475
 
2476
  def _create_search_bar(self):
2477
+ """Create the search bar (compact version)."""
2478
+ search_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
2479
+ search_frame.grid(row=1, column=0, sticky="ew", pady=(0, 5))
2480
 
2481
  search_icon = ctk.CTkLabel(
2482
  search_frame,
2483
  text="πŸ”",
2484
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2485
  text_color=TEXT_MUTED,
2486
  )
2487
+ search_icon.pack(side="left", padx=(0, 6))
2488
 
2489
  self.search_entry = ctk.CTkEntry(
2490
  search_frame,
 
2494
  border_color=BORDER_COLOR,
2495
  text_color=TEXT_PRIMARY,
2496
  placeholder_text_color=TEXT_MUTED,
2497
+ height=28,
2498
  )
2499
  self.search_entry.pack(side="left", fill="x", expand=True)
2500
  self.search_entry.bind("<KeyRelease>", self._on_search_changed)
 
2503
  clear_btn = ctk.CTkButton(
2504
  search_frame,
2505
  text="Γ—",
2506
+ font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2507
  fg_color="transparent",
2508
  hover_color=BG_HOVER,
2509
  text_color=TEXT_MUTED,
2510
+ width=28,
2511
+ height=28,
2512
  command=self._clear_search,
2513
  )
2514
  clear_btn.pack(side="left")
 
2517
  """Create the synchronized model list panel."""
2518
  # Use the virtual list implementation for performance
2519
  self.model_list_panel = VirtualSyncModelLists(
2520
+ self.content_frame,
2521
  on_model_click=self._on_model_clicked,
2522
  on_model_right_click=self._on_model_right_clicked,
2523
  )
2524
+ self.model_list_panel.grid(row=2, column=0, sticky="nsew", pady=(0, 5))
2525
 
2526
  def _create_rule_panels(self):
2527
  """Create the ignore and whitelist rule panels."""
2528
+ self.rules_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
2529
+ self.rules_frame.grid(row=3, column=0, sticky="nsew", pady=(0, 5))
2530
+ self.rules_frame.grid_columnconfigure(0, weight=1)
2531
+ self.rules_frame.grid_columnconfigure(1, weight=1)
2532
+ self.rules_frame.grid_rowconfigure(0, weight=1)
2533
 
2534
  # Ignore panel
2535
  self.ignore_panel = RulePanel(
2536
+ self.rules_frame,
2537
  title="🚫 Ignore Rules",
2538
  rule_type="ignore",
2539
  on_rules_changed=self._on_rules_changed,
 
2546
 
2547
  # Whitelist panel
2548
  self.whitelist_panel = RulePanel(
2549
+ self.rules_frame,
2550
  title="βœ“ Whitelist Rules",
2551
  rule_type="whitelist",
2552
  on_rules_changed=self._on_rules_changed,
 
2558
  self.whitelist_panel.set_delete_callback(self._remove_whitelist_pattern)
2559
 
2560
  def _create_status_bar(self):
2561
+ """Create the status bar showing available count and action buttons (compact)."""
2562
  # Combined status bar and action buttons in one row
2563
+ self.status_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
2564
+ self.status_frame.grid(row=4, column=0, sticky="ew", pady=(3, 3))
2565
 
2566
+ # Status label (left side, smaller font)
2567
  self.status_label = ctk.CTkLabel(
2568
  self.status_frame,
2569
  text="Select a provider to begin",
2570
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2571
  text_color=TEXT_SECONDARY,
2572
  )
2573
  self.status_label.pack(side="left")
 
2579
  font=(FONT_FAMILY, FONT_SIZE_SMALL),
2580
  text_color=ACCENT_YELLOW,
2581
  )
2582
+ self.unsaved_label.pack(side="left", padx=(10, 0))
2583
 
2584
+ # Buttons (right side, smaller)
2585
  # Discard button
2586
  discard_btn = ctk.CTkButton(
2587
  self.status_frame,
2588
  text="↩️ Discard",
2589
+ font=(FONT_FAMILY, FONT_SIZE_SMALL),
2590
  fg_color=BG_SECONDARY,
2591
  hover_color=BG_HOVER,
2592
  border_width=1,
2593
  border_color=BORDER_COLOR,
2594
+ width=85,
2595
+ height=26,
2596
  command=self._discard_changes,
2597
  )
2598
+ discard_btn.pack(side="right", padx=(8, 0))
2599
 
2600
  # Save button
2601
  save_btn = ctk.CTkButton(
2602
  self.status_frame,
2603
  text="πŸ’Ύ Save",
2604
+ font=(FONT_FAMILY, FONT_SIZE_SMALL, "bold"),
2605
  fg_color=ACCENT_GREEN,
2606
  hover_color="#27ae60",
2607
+ width=75,
2608
+ height=26,
2609
  command=self._save_changes,
2610
  )
2611
  save_btn.pack(side="right")