flutter-dev 0.1.6__tar.gz → 0.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/CHANGELOG.md +13 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/PKG-INFO +15 -3
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/README.md +14 -2
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/gui.py +137 -30
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/pyproject.toml +1 -1
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/tests/test_gui.py +85 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/.env.example +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/LICENSE +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/MANIFEST.in +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/common_utils.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/core/__init__.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/core/constants.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/core/state.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/create_page.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/fdev.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/flutter_dev.egg-info/SOURCES.txt +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/gemini_api.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/git_diff_output_editor.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/install_legacy.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/__init__.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/ai.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/app.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/brew.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/build.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/datetime.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/device.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/doctor.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/git.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/git_account.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/localization.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/merge.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/mirror.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/package.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/project.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/managers/web_deploy.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/requirements.txt +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/setup.cfg +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/setup.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/switch_ai.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/tests/test_fdev_cli.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/tests/test_localization.py +0 -0
- {flutter_dev-0.1.6 → flutter_dev-0.1.8}/tests/test_package_commands.py +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.8 - 2026-06-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Made the dashboard command output panel visibly framed with a ready message so the terminal/log area does not appear missing before commands produce output.
|
|
10
|
+
|
|
11
|
+
## 0.1.7 - 2026-06-25
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Reworked the Tkinter dashboard to use classic Tk widgets with explicit colors so `fdev` stays visible on macOS Xcode/system Python runtimes that can render `ttk` widgets as a blank popup.
|
|
16
|
+
- Silenced the macOS system Tk deprecation warning before launching the dashboard.
|
|
17
|
+
|
|
5
18
|
## 0.1.6 - 2026-06-25
|
|
6
19
|
|
|
7
20
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flutter-dev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: A cross-platform CLI toolkit for Flutter development workflows.
|
|
5
5
|
Author: Royal Court BD
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,7 +42,7 @@ The package is published on PyPI as `flutter-dev` and installs console commands
|
|
|
42
42
|
python -m pip install flutter-dev
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Current package metadata declares version `0.1.
|
|
45
|
+
Current package metadata declares version `0.1.8`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
|
|
46
46
|
|
|
47
47
|
## Key Features
|
|
48
48
|
|
|
@@ -53,7 +53,7 @@ Current package metadata declares version `0.1.6`, Python `>=3.8`, MIT license,
|
|
|
53
53
|
- AI-assisted Git commits using Groq, Mistral, SambaNova, or OpenRouter-compatible chat completion APIs.
|
|
54
54
|
- Git workflow helpers for version tags, branch sync, deployment branch merge, discard, and GitHub account management through `gh`.
|
|
55
55
|
- Feature/page generator that creates a `lib/features/<name>` structure using the project's presenter/base-state pattern.
|
|
56
|
-
- Small Tkinter desktop dashboard opened by running `fdev` with no command, with live
|
|
56
|
+
- Small Tkinter desktop dashboard opened by running `fdev` with no command, with a framed live command output panel.
|
|
57
57
|
- Environment diagnostics through `fdev doctor`.
|
|
58
58
|
- Small utilities for Homebrew cleanup, file merging from `paths.txt`, AI provider switching, PyPI self-upgrade/delete, and git diff prompt export.
|
|
59
59
|
|
|
@@ -433,6 +433,18 @@ fdev delete
|
|
|
433
433
|
|
|
434
434
|
Both commands print the detected OS and exact Python executable before running. They use that executable with `-m pip`, not a hardcoded `python`, `python3`, `pip`, or `pip3`, so the active venv/global Python that launched `fdev` is the environment that gets upgraded or deleted. On Windows, `fdev delete` and `fdev upgrade` schedule the same `sys.executable -m pip ...` operation in a delayed command process so the currently running console launcher can exit first.
|
|
435
435
|
|
|
436
|
+
### Blank Dashboard On macOS
|
|
437
|
+
|
|
438
|
+
If `fdev` prints an Xcode/system Tk warning or opens a blank dashboard, upgrade to `flutter-dev` 0.1.7 or newer:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
fdev upgrade
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
`fdev` prints the Python executable it is using. If that path is `/Applications/Xcode.app/Contents/Developer/usr/bin/python3`, the CLI is running on Xcode's bundled Python and system Tk; 0.1.7 uses a more conservative Tk UI path for that runtime.
|
|
445
|
+
|
|
446
|
+
If the dashboard opens but the command output area looks missing or empty, upgrade to `flutter-dev` 0.1.8 or newer.
|
|
447
|
+
|
|
436
448
|
### Flutter Project Not Detected
|
|
437
449
|
|
|
438
450
|
Run project-specific commands from the directory that contains `pubspec.yaml`.
|
|
@@ -8,7 +8,7 @@ The package is published on PyPI as `flutter-dev` and installs console commands
|
|
|
8
8
|
python -m pip install flutter-dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Current package metadata declares version `0.1.
|
|
11
|
+
Current package metadata declares version `0.1.8`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
|
|
12
12
|
|
|
13
13
|
## Key Features
|
|
14
14
|
|
|
@@ -19,7 +19,7 @@ Current package metadata declares version `0.1.6`, Python `>=3.8`, MIT license,
|
|
|
19
19
|
- AI-assisted Git commits using Groq, Mistral, SambaNova, or OpenRouter-compatible chat completion APIs.
|
|
20
20
|
- Git workflow helpers for version tags, branch sync, deployment branch merge, discard, and GitHub account management through `gh`.
|
|
21
21
|
- Feature/page generator that creates a `lib/features/<name>` structure using the project's presenter/base-state pattern.
|
|
22
|
-
- Small Tkinter desktop dashboard opened by running `fdev` with no command, with live
|
|
22
|
+
- Small Tkinter desktop dashboard opened by running `fdev` with no command, with a framed live command output panel.
|
|
23
23
|
- Environment diagnostics through `fdev doctor`.
|
|
24
24
|
- Small utilities for Homebrew cleanup, file merging from `paths.txt`, AI provider switching, PyPI self-upgrade/delete, and git diff prompt export.
|
|
25
25
|
|
|
@@ -399,6 +399,18 @@ fdev delete
|
|
|
399
399
|
|
|
400
400
|
Both commands print the detected OS and exact Python executable before running. They use that executable with `-m pip`, not a hardcoded `python`, `python3`, `pip`, or `pip3`, so the active venv/global Python that launched `fdev` is the environment that gets upgraded or deleted. On Windows, `fdev delete` and `fdev upgrade` schedule the same `sys.executable -m pip ...` operation in a delayed command process so the currently running console launcher can exit first.
|
|
401
401
|
|
|
402
|
+
### Blank Dashboard On macOS
|
|
403
|
+
|
|
404
|
+
If `fdev` prints an Xcode/system Tk warning or opens a blank dashboard, upgrade to `flutter-dev` 0.1.7 or newer:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
fdev upgrade
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`fdev` prints the Python executable it is using. If that path is `/Applications/Xcode.app/Contents/Developer/usr/bin/python3`, the CLI is running on Xcode's bundled Python and system Tk; 0.1.7 uses a more conservative Tk UI path for that runtime.
|
|
411
|
+
|
|
412
|
+
If the dashboard opens but the command output area looks missing or empty, upgrade to `flutter-dev` 0.1.8 or newer.
|
|
413
|
+
|
|
402
414
|
### Flutter Project Not Detected
|
|
403
415
|
|
|
404
416
|
Run project-specific commands from the directory that contains `pubspec.yaml`.
|
|
@@ -5,6 +5,7 @@ Small Tkinter GUI for running fdev commands with live logs.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
import shlex
|
|
9
10
|
import subprocess
|
|
10
11
|
import sys
|
|
@@ -37,6 +38,23 @@ ANSI_COLORS = {
|
|
|
37
38
|
97: "bright_white",
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
UI_BG = "#f8fafc"
|
|
42
|
+
SIDEBAR_BG = "#e5e7eb"
|
|
43
|
+
PANEL_BG = "#ffffff"
|
|
44
|
+
TEXT_FG = "#111827"
|
|
45
|
+
MUTED_FG = "#475569"
|
|
46
|
+
BUTTON_BG = "#e2e8f0"
|
|
47
|
+
BUTTON_ACTIVE_BG = "#cbd5e1"
|
|
48
|
+
LOG_BG = "#111827"
|
|
49
|
+
LOG_FG = "#e5e7eb"
|
|
50
|
+
LOG_BORDER = "#475569"
|
|
51
|
+
LOG_READY_MESSAGE = "[fdev] Ready. Command output will appear here.\n"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def prepare_tk_environment():
|
|
55
|
+
"""Prepare Tk for older macOS system Tk runtimes before importing tkinter."""
|
|
56
|
+
os.environ.setdefault("TK_SILENCE_DEPRECATION", "1")
|
|
57
|
+
|
|
40
58
|
|
|
41
59
|
def _apply_ansi_codes(codes: List[int], current_tag: Optional[str]) -> Optional[str]:
|
|
42
60
|
if not codes:
|
|
@@ -303,86 +321,150 @@ class FdevGui:
|
|
|
303
321
|
|
|
304
322
|
def _build_widgets(self):
|
|
305
323
|
import tkinter as tk
|
|
306
|
-
|
|
324
|
+
|
|
325
|
+
self.root.configure(bg=UI_BG)
|
|
307
326
|
|
|
308
327
|
self.root.columnconfigure(1, weight=1)
|
|
309
328
|
self.root.rowconfigure(0, weight=1)
|
|
310
329
|
|
|
311
|
-
sidebar =
|
|
330
|
+
sidebar = tk.Frame(self.root, bg=SIDEBAR_BG, padx=10, pady=10)
|
|
312
331
|
sidebar.grid(row=0, column=0, sticky="ns")
|
|
313
332
|
|
|
314
|
-
title =
|
|
333
|
+
title = tk.Label(
|
|
334
|
+
sidebar,
|
|
335
|
+
text="fdev",
|
|
336
|
+
font=("TkDefaultFont", 16, "bold"),
|
|
337
|
+
bg=SIDEBAR_BG,
|
|
338
|
+
fg=TEXT_FG,
|
|
339
|
+
)
|
|
315
340
|
title.grid(row=0, column=0, sticky="w", pady=(0, 10))
|
|
316
341
|
|
|
317
342
|
for index, section_name in enumerate(self.sections, start=1):
|
|
318
|
-
button =
|
|
343
|
+
button = tk.Button(
|
|
319
344
|
sidebar,
|
|
320
345
|
text=section_name,
|
|
321
346
|
command=lambda name=section_name: self._show_section(name),
|
|
347
|
+
bg=BUTTON_BG,
|
|
348
|
+
fg=TEXT_FG,
|
|
349
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
350
|
+
activeforeground=TEXT_FG,
|
|
351
|
+
relief="raised",
|
|
352
|
+
bd=1,
|
|
353
|
+
padx=10,
|
|
354
|
+
pady=4,
|
|
322
355
|
)
|
|
323
356
|
button.grid(row=index, column=0, sticky="ew", pady=3)
|
|
324
357
|
|
|
325
|
-
content =
|
|
358
|
+
content = tk.Frame(self.root, bg=UI_BG, padx=8, pady=10)
|
|
326
359
|
content.grid(row=0, column=1, sticky="nsew")
|
|
327
360
|
content.columnconfigure(0, weight=1)
|
|
328
361
|
content.rowconfigure(1, weight=1)
|
|
329
362
|
|
|
330
|
-
header =
|
|
363
|
+
header = tk.Frame(content, bg=UI_BG)
|
|
331
364
|
header.grid(row=0, column=0, sticky="ew", pady=(0, 8))
|
|
332
365
|
header.columnconfigure(0, weight=1)
|
|
333
366
|
|
|
334
|
-
self.section_label =
|
|
367
|
+
self.section_label = tk.Label(
|
|
368
|
+
header,
|
|
369
|
+
text="",
|
|
370
|
+
font=("TkDefaultFont", 14, "bold"),
|
|
371
|
+
bg=UI_BG,
|
|
372
|
+
fg=TEXT_FG,
|
|
373
|
+
)
|
|
335
374
|
self.section_label.grid(row=0, column=0, sticky="w")
|
|
336
375
|
|
|
337
|
-
self.status_label =
|
|
376
|
+
self.status_label = tk.Label(header, text="Ready", bg=UI_BG, fg=MUTED_FG)
|
|
338
377
|
self.status_label.grid(row=0, column=1, sticky="e")
|
|
339
378
|
|
|
340
|
-
body =
|
|
379
|
+
body = tk.Frame(content, bg=UI_BG)
|
|
341
380
|
body.grid(row=1, column=0, sticky="nsew")
|
|
342
381
|
body.columnconfigure(0, weight=1)
|
|
343
382
|
body.rowconfigure(0, weight=0)
|
|
344
383
|
body.rowconfigure(1, weight=1)
|
|
345
384
|
|
|
346
|
-
self.command_frame =
|
|
385
|
+
self.command_frame = tk.Frame(body, bg=UI_BG, pady=6)
|
|
347
386
|
self.command_frame.grid(row=0, column=0, sticky="ew")
|
|
348
387
|
self.command_frame.columnconfigure(0, weight=1)
|
|
349
388
|
self.command_frame.columnconfigure(1, weight=1)
|
|
350
389
|
|
|
351
|
-
log_frame =
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
390
|
+
self.log_frame = tk.Frame(
|
|
391
|
+
body,
|
|
392
|
+
bg=LOG_BG,
|
|
393
|
+
bd=1,
|
|
394
|
+
relief="solid",
|
|
395
|
+
highlightbackground=LOG_BORDER,
|
|
396
|
+
highlightcolor=LOG_BORDER,
|
|
397
|
+
highlightthickness=1,
|
|
398
|
+
)
|
|
399
|
+
self.log_frame.grid(row=1, column=0, sticky="nsew", pady=(6, 0))
|
|
400
|
+
self.log_frame.columnconfigure(0, weight=1)
|
|
401
|
+
self.log_frame.rowconfigure(1, weight=1)
|
|
402
|
+
|
|
403
|
+
self.log_header = tk.Label(
|
|
404
|
+
self.log_frame,
|
|
405
|
+
text="Command Output",
|
|
406
|
+
bg=LOG_BG,
|
|
407
|
+
fg=LOG_FG,
|
|
408
|
+
anchor="w",
|
|
409
|
+
padx=8,
|
|
410
|
+
pady=4,
|
|
411
|
+
)
|
|
412
|
+
self.log_header.grid(row=0, column=0, columnspan=2, sticky="ew")
|
|
355
413
|
|
|
356
414
|
self.log_text = tk.Text(
|
|
357
|
-
log_frame,
|
|
415
|
+
self.log_frame,
|
|
358
416
|
height=12,
|
|
359
417
|
wrap="word",
|
|
360
418
|
state="disabled",
|
|
361
|
-
bg=
|
|
362
|
-
fg=
|
|
363
|
-
insertbackground=
|
|
419
|
+
bg=LOG_BG,
|
|
420
|
+
fg=LOG_FG,
|
|
421
|
+
insertbackground=LOG_FG,
|
|
422
|
+
relief="flat",
|
|
423
|
+
bd=0,
|
|
424
|
+
padx=8,
|
|
425
|
+
pady=6,
|
|
426
|
+
highlightthickness=0,
|
|
364
427
|
)
|
|
365
|
-
self.log_text.grid(row=
|
|
428
|
+
self.log_text.grid(row=1, column=0, sticky="nsew")
|
|
366
429
|
|
|
367
|
-
scrollbar =
|
|
368
|
-
scrollbar.grid(row=
|
|
430
|
+
scrollbar = tk.Scrollbar(self.log_frame, orient="vertical", command=self.log_text.yview)
|
|
431
|
+
scrollbar.grid(row=1, column=1, sticky="ns")
|
|
369
432
|
self.log_text.configure(yscrollcommand=scrollbar.set)
|
|
370
433
|
self._configure_log_tags()
|
|
434
|
+
self._append_log(LOG_READY_MESSAGE)
|
|
371
435
|
|
|
372
|
-
input_bar =
|
|
436
|
+
input_bar = tk.Frame(content, bg=UI_BG)
|
|
373
437
|
input_bar.grid(row=2, column=0, sticky="ew", pady=(8, 0))
|
|
374
438
|
input_bar.columnconfigure(0, weight=1)
|
|
375
439
|
|
|
376
|
-
self.input_entry =
|
|
440
|
+
self.input_entry = tk.Entry(input_bar, bg=PANEL_BG, fg=TEXT_FG, insertbackground=TEXT_FG)
|
|
377
441
|
self.input_entry.grid(row=0, column=0, sticky="ew", padx=(0, 6))
|
|
378
442
|
self.input_entry.bind("<Return>", lambda _event: self._send_input())
|
|
379
443
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
444
|
+
tk.Button(
|
|
445
|
+
input_bar,
|
|
446
|
+
text="Send Input",
|
|
447
|
+
command=self._send_input,
|
|
448
|
+
bg=BUTTON_BG,
|
|
449
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
450
|
+
).grid(row=0, column=1, padx=(0, 6))
|
|
451
|
+
tk.Button(
|
|
452
|
+
input_bar,
|
|
453
|
+
text="Stop",
|
|
454
|
+
command=self._stop_process,
|
|
455
|
+
bg=BUTTON_BG,
|
|
456
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
457
|
+
).grid(row=0, column=2, padx=(0, 6))
|
|
458
|
+
tk.Button(
|
|
459
|
+
input_bar,
|
|
460
|
+
text="Clear",
|
|
461
|
+
command=self._clear_log,
|
|
462
|
+
bg=BUTTON_BG,
|
|
463
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
464
|
+
).grid(row=0, column=3)
|
|
383
465
|
|
|
384
466
|
def _show_section(self, section_name: str):
|
|
385
|
-
|
|
467
|
+
import tkinter as tk
|
|
386
468
|
|
|
387
469
|
self.current_section = section_name
|
|
388
470
|
self.section_label.configure(text=section_name)
|
|
@@ -394,18 +476,41 @@ class FdevGui:
|
|
|
394
476
|
row = index // 2
|
|
395
477
|
column = index % 2
|
|
396
478
|
|
|
397
|
-
container =
|
|
479
|
+
container = tk.Frame(
|
|
480
|
+
self.command_frame,
|
|
481
|
+
bg=PANEL_BG,
|
|
482
|
+
bd=1,
|
|
483
|
+
relief="solid",
|
|
484
|
+
padx=6,
|
|
485
|
+
pady=6,
|
|
486
|
+
)
|
|
398
487
|
container.grid(row=row, column=column, sticky="nsew", padx=4, pady=4)
|
|
399
488
|
container.columnconfigure(0, weight=1)
|
|
400
489
|
|
|
401
|
-
button =
|
|
490
|
+
button = tk.Button(
|
|
402
491
|
container,
|
|
403
492
|
text=command.label,
|
|
404
493
|
command=lambda item=command: self._run_command(item),
|
|
494
|
+
bg=BUTTON_BG,
|
|
495
|
+
fg=TEXT_FG,
|
|
496
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
497
|
+
activeforeground=TEXT_FG,
|
|
498
|
+
relief="raised",
|
|
499
|
+
bd=1,
|
|
500
|
+
padx=8,
|
|
501
|
+
pady=4,
|
|
405
502
|
)
|
|
406
503
|
button.grid(row=0, column=0, sticky="ew")
|
|
407
504
|
|
|
408
|
-
description =
|
|
505
|
+
description = tk.Label(
|
|
506
|
+
container,
|
|
507
|
+
text=command.description,
|
|
508
|
+
wraplength=260,
|
|
509
|
+
bg=PANEL_BG,
|
|
510
|
+
fg=MUTED_FG,
|
|
511
|
+
justify="left",
|
|
512
|
+
anchor="w",
|
|
513
|
+
)
|
|
409
514
|
description.grid(row=1, column=0, sticky="w", pady=(4, 0))
|
|
410
515
|
|
|
411
516
|
def _run_command(self, command: GuiCommand):
|
|
@@ -489,6 +594,7 @@ class FdevGui:
|
|
|
489
594
|
self.log_text.configure(state="normal")
|
|
490
595
|
self.log_text.delete("1.0", "end")
|
|
491
596
|
self.log_text.configure(state="disabled")
|
|
597
|
+
self._append_log(LOG_READY_MESSAGE)
|
|
492
598
|
|
|
493
599
|
def _send_input(self):
|
|
494
600
|
text = self.input_entry.get()
|
|
@@ -523,6 +629,7 @@ class FdevGui:
|
|
|
523
629
|
|
|
524
630
|
def launch_gui() -> int:
|
|
525
631
|
"""Launch the fdev Tkinter dashboard."""
|
|
632
|
+
prepare_tk_environment()
|
|
526
633
|
try:
|
|
527
634
|
import tkinter as tk
|
|
528
635
|
except ImportError:
|
|
@@ -2,10 +2,12 @@ import sys
|
|
|
2
2
|
import unittest
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from tkinter import TclError
|
|
5
|
+
from unittest.mock import patch
|
|
5
6
|
|
|
6
7
|
from managers.gui import (
|
|
7
8
|
FdevGui,
|
|
8
9
|
GuiCommand,
|
|
10
|
+
prepare_tk_environment,
|
|
9
11
|
parse_ansi_segments,
|
|
10
12
|
build_fdev_command,
|
|
11
13
|
get_gui_sections,
|
|
@@ -144,6 +146,14 @@ class GuiCatalogTests(unittest.TestCase):
|
|
|
144
146
|
["sync", "dev", "main"],
|
|
145
147
|
)
|
|
146
148
|
|
|
149
|
+
def test_prepare_tk_environment_silences_macos_system_tk_warning(self):
|
|
150
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
151
|
+
prepare_tk_environment()
|
|
152
|
+
|
|
153
|
+
import os
|
|
154
|
+
|
|
155
|
+
self.assertEqual(os.environ["TK_SILENCE_DEPRECATION"], "1")
|
|
156
|
+
|
|
147
157
|
def test_dashboard_uses_stable_grid_layout_without_paned_window(self):
|
|
148
158
|
try:
|
|
149
159
|
import tkinter as tk
|
|
@@ -172,6 +182,81 @@ class GuiCatalogTests(unittest.TestCase):
|
|
|
172
182
|
finally:
|
|
173
183
|
root.destroy()
|
|
174
184
|
|
|
185
|
+
def test_dashboard_maps_initial_widgets_after_mainloop_starts(self):
|
|
186
|
+
try:
|
|
187
|
+
import tkinter as tk
|
|
188
|
+
except ImportError:
|
|
189
|
+
self.skipTest("Tkinter is not available")
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
root = tk.Tk()
|
|
193
|
+
except TclError as error:
|
|
194
|
+
self.skipTest(f"Tk display is not available: {error}")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
app = FdevGui(root)
|
|
198
|
+
root.after(100, root.quit)
|
|
199
|
+
app.run()
|
|
200
|
+
|
|
201
|
+
self.assertTrue(app.command_frame.winfo_ismapped())
|
|
202
|
+
self.assertTrue(app.log_text.winfo_ismapped())
|
|
203
|
+
self.assertGreater(app.command_frame.winfo_width(), 100)
|
|
204
|
+
self.assertGreater(app.log_text.winfo_width(), 100)
|
|
205
|
+
finally:
|
|
206
|
+
root.destroy()
|
|
207
|
+
|
|
208
|
+
def test_dashboard_keeps_terminal_panel_visible_on_launch(self):
|
|
209
|
+
try:
|
|
210
|
+
import tkinter as tk
|
|
211
|
+
except ImportError:
|
|
212
|
+
self.skipTest("Tkinter is not available")
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
root = tk.Tk()
|
|
216
|
+
except TclError as error:
|
|
217
|
+
self.skipTest(f"Tk display is not available: {error}")
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
app = FdevGui(root)
|
|
221
|
+
root.update()
|
|
222
|
+
|
|
223
|
+
self.assertTrue(app.log_frame.winfo_ismapped())
|
|
224
|
+
self.assertEqual(app.log_frame.cget("relief"), "solid")
|
|
225
|
+
self.assertIn("[fdev] Ready", app.log_text.get("1.0", "end"))
|
|
226
|
+
finally:
|
|
227
|
+
root.destroy()
|
|
228
|
+
|
|
229
|
+
def test_dashboard_uses_classic_tk_widgets_for_system_tk_compatibility(self):
|
|
230
|
+
try:
|
|
231
|
+
import tkinter as tk
|
|
232
|
+
except ImportError:
|
|
233
|
+
self.skipTest("Tkinter is not available")
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
root = tk.Tk()
|
|
237
|
+
except TclError as error:
|
|
238
|
+
self.skipTest(f"Tk display is not available: {error}")
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
app = FdevGui(root)
|
|
242
|
+
root.update_idletasks()
|
|
243
|
+
|
|
244
|
+
widget_classes = {
|
|
245
|
+
widget.winfo_class()
|
|
246
|
+
for widget in _walk_widgets(root)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
self.assertFalse(
|
|
250
|
+
{"TButton", "TEntry", "TFrame", "TLabel", "TScrollbar"} & widget_classes
|
|
251
|
+
)
|
|
252
|
+
self.assertIn("Button", widget_classes)
|
|
253
|
+
self.assertIn("Label", widget_classes)
|
|
254
|
+
self.assertIn("Entry", widget_classes)
|
|
255
|
+
self.assertIn("Scrollbar", widget_classes)
|
|
256
|
+
self.assertGreater(app.command_frame.winfo_width(), 100)
|
|
257
|
+
finally:
|
|
258
|
+
root.destroy()
|
|
259
|
+
|
|
175
260
|
|
|
176
261
|
def _walk_widgets(widget):
|
|
177
262
|
yield widget
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|