Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .pi-tasks/.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Keep committed task files out of pi find/grep discovery (host model + research
# workers) while git still tracks them. .ignore is read by fd/ripgrep, not by
# git, so tasks stay committable. Managed by pi-task.
*
229 changes: 229 additions & 0 deletions .pi-tasks/TASK_0001.md

Large diffs are not rendered by default.

475 changes: 475 additions & 0 deletions .pi-tasks/TASK_0002.md

Large diffs are not rendered by default.

176 changes: 176 additions & 0 deletions .pi-tasks/TASK_0003.md

Large diffs are not rendered by default.

199 changes: 199 additions & 0 deletions .pi-tasks/TASK_0004.md

Large diffs are not rendered by default.

175 changes: 175 additions & 0 deletions .pi-tasks/TASK_0005.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
id: TASK_0005
state: completed
phase: done
created_at: 2026-07-03T03:07:02.229Z
updated_at: 2026-07-03T07:37:29.871Z
title: The mkpfs GUI window must be responsive to resizing and window-manager maximize/restore events. When the user resizes or maximizes the window, all layout geometry (widget positions, sizes, padding, column/row weights) must adapt proportionally so that no content is clipped or hidden and the overall layout remains usable. The window's initial size and minimum size constraints should be reasonable, and the window must support the native maximize button (green traffic-light dot on macOS) and window-manager maximize (double-click title bar or keyboard shortcut).
label: The mkpfs GUI window must be responsive to resizing and window-manager…
---

## raw prompt

Make the window responsive, resizable, and maximizable | decisions (explicit user choices β€” these OVERRIDE the spec doc wherever they conflict; follow them exactly): keep CustomTkinter/tkinter to minimize scope, preserve current behavior and packaging

## refined prompt

GOAL
The mkpfs GUI window must be responsive to resizing and window-manager maximize/restore events. When the user resizes or maximizes the window, all layout geometry (widget positions, sizes, padding, column/row weights) must adapt proportionally so that no content is clipped or hidden and the overall layout remains usable. The window's initial size and minimum size constraints should be reasonable, and the window must support the native maximize button (green traffic-light dot on macOS) and window-manager maximize (double-click title bar or keyboard shortcut).

CONSTRAINTS
- Must use only CustomTkinter (and tkinter) β€” no additional GUI frameworks or layout backends.
- Must preserve current behavior: all existing widget interactions, data flows, command execution, progress display, and packaging (e.g., PyInstaller/standalone build) must remain unchanged.
- Must not touch any logic in the shared mkpfs metadata/progress hooks (Step 1), the package-split modules (Step 2), the command navigation/option forms (Step 3), or the output naming/footer versioning/progress mirroring (Step 4).
- Must not introduce new user-facing buttons, menus, or settings for layout control β€” responsiveness must be automatic via CTk grid/pack weight configuration and event binding.
- Must not alter the app's startup sequence, main entry point signature, or any command-line interface.
- Must not modify or remove any existing widget β€” only adjust grid row/column weights, sticky settings, padding, and bind to `<Configure>` or `<Map>` events as needed.

KNOWN-UNKNOWNS
- What is the current minimum window size (or should one be set if none exists)?
- Should specific widgets (e.g., scrollable frames, text output panes, tree views) have minimum height/width constraints?
- Should the window size and position be persisted across sessions (via a config file or tkinter geometry-saving)?
- Is there any widget that should NOT grow/shrink (e.g., a fixed-size logo or button row)? If so, how should its behavior differ on maximize?

EXTERNAL-DEPENDENCIES
(none β€” pure local CustomTkinter/tkinter changes with no third-party service dependencies)

## verified tooling

ruff check mkpfs tests
pytest
uv build

## research

FILES
mkpfs/gui.py Main GUI shim β€” single-file layout; all widget creation, grid/pack config, and geometry logic is here
mkpfs/gui/__init__.py Package init, re-exports MkPFSApp from shim
mkpfs/gui/app.py Thin faΓ§ade re-exporting MkPPFSApp/main from mkpfs.gui
mkpfs/gui/__main__.py Entry point for `python -m mkpfs.gui`
mkpfs/gui/widgets.py Custom widget classes (CTkScrollableFrame subclass, Treeview wrapper, etc.)
mkpfs/gui/panels.py Sub-panel classes (command panels, output panels, progress panels)
mkpfs/gui/theme.py Theme setup (CTk appearance mode, color, font config)
mkpfs/gui/i18n.py String resource lookups β€” may affect label widths / padding assumptions
pyproject.toml GUI optional-dependency group; minimum-required CTk version
tests/mkpfs/test_gui.py (if exists) GUI unit tests β€” may need assertion updates for new geometry
scripts/pyinstaller-hook-customtkinter.py Packaging hook β€” irrelevant unless resize hooks change import tree (unlikely)

APIS
(degraded: research APIS worker timed out after restarts; this section may be incomplete)

Now let me check the theme and i18n files for any layout-related constants:

CONTEXT
- Window is created in `mkpfs/gui/app.py`, class `App(CTk)` β€” reads `mkpfs/gui/app.py` first to understand current layout setup.
- The main layout uses a `CTkTabview` with tabs "Pack File", "Pack Folder", "Verify" β€” each tab contains a `CTkScrollableFrame` as body β€” `mkpfs/gui/panels.py` constructs these panels.
- `mkpfs/gui/app.py` does NOT currently call `app.minsize()` or `app.geometry()` β€” window starts at default CTk size (~200Γ—200) which is too small; no minimum size is enforced.
- `mkpfs/gui/app.py` does NOT bind to `<Configure>` for resize handling β€” no automatic layout adjustment exists.
- Panels in `mkpfs/gui/panels.py` use `.pack(fill="both", expand=True)` on outer containers but inner content uses mixed `.grid()` and `.pack()` without `sticky="nsew"` or row/column weight configuration β€” inner content will not stretch.
- `mkpfs/gui/panels.py` contains `ResultsPanel` with a `CTkTextbox` for output β€” if this textbox lacks `expand=True` or weight, it won't grow on resize.
- `mkpfs/gui/widgets.py` contains custom widget classes β€” any `.grid()` calls there likely lack `sticky="nsew"` and `rowconfigure`/`columnconfigure`.
- `mkpfs/gui/panels.py` imports `FilePanel`, `FolderPanel`, `VerifyPanel` β€” each constructs a command-specific form β€” read each to find fixed-size widgets (e.g., entry widths set by `width=...` rather than sticky+weight).
- GUI entry point is at `mkpfs/gui/__main__.py` which calls `App().mainloop()` β€” `app.mainloop()` is in `app.py` ; can add geometry/min-size there without changing entry signature.
- `mkpfs/pyproject.toml` lists `customtkinter>=5.2.2` β€” CTk `grid_columnconfigure` and `grid_rowconfigure` with `weight` are the mechanism for proportional resizing.
- Theme file at `mkpfs/gui/theme.py` β€” no themes set fixed geometry constraints.
- i18n at `mkpfs/gui/i18n.py` β€” no geometry implications.
- No existing session-persistence or config file mechanism for geometry; adding one would touch non-layout code β€” avoid per constraint.
- `tests/` contain unit tests for CLI/backends, no GUI tests β€” no GUI test file exists, so no test-refactoring needed.

VERIFIED-TOOLING
ruff check mkpfs tests
pytest
uv build

## grill Q&A

(no questions produced)

## spec

GOAL
The mkpfs GUI window must respond to resizing (drag edges/corners) and window-manager maximize/restore events so that all layout geometry adapts proportionally, no content is clipped or hidden, and the overall layout remains usable at any size. The window must have a sensible initial size and minimum size, support the native maximize button (green traffic-light dot on macOS) and window-manager maximize (double-click title bar or keyboard shortcut), and all changes must be confined to grid/pack weight configuration, sticky settings, padding, and event binding in the GUI source files.

CONSTRAINTS
- Must use only CustomTkinter (and tkinter) β€” no additional GUI frameworks or layout backends.
- Must preserve all existing widget interactions, data flows, command execution, progress display, and packaging (e.g., PyInstaller/standalone build) unchanged.
- Must not touch any logic in the shared mkpfs metadata/progress hooks, the package-split modules, the command navigation/option forms, or the output naming/footer versioning/progress mirroring.
- Must not introduce new user-facing buttons, menus, or settings for layout control β€” responsiveness must be automatic via CTk grid/pack weight configuration and event binding.
- Must not alter the app's startup sequence, main entry point signature, or any command-line interface.
- Must not modify or remove any existing widget β€” only adjust grid row/column weights, sticky settings, padding, and bind to `<Configure>` or `<Map>` events as needed.
- The window's initial geometry must be set (e.g., `app.geometry("1200x800")`) and a minimum size must be enforced (e.g., `app.minsize(800, 600)`).
- The main `CTkTabview` and every `CTkScrollableFrame` inside each tab must have grid weights configured so that they expand proportionally on resize β€” inner content (text boxes, tree views, option forms) must use `sticky="nsew"` and row/column weight.
- For every `.grid()` call on a widget that should grow with its parent, add `sticky="nsew"` and ensure the containing parent has a non-zero row/column weight. For every `.pack(fill=...)` call on a growable widget, prefer `fill="both", expand=True`.
- No widget that should grow proportionally may retain an explicit `width=` that prevents expansion; such widths must be replaced by weight+sticky configuration.
- The `<Configure>` event of the window must be bound (or grid weights must already cause automatic proportional resizing via CTk/tkinter geometry propagation) β€” no additional manual resize handler is required if CTk's own grid propagation handles it.

ACCEPTANCE
- The window opens at 1200Γ—800 pixels (or another reasonable size set via `app.geometry()`).
- The window cannot be resized smaller than 800Γ—600 (or another reasonable minimum set via `app.minsize()`).
- Dragging a window edge or corner resizes the entire layout proportionally: the tab view expands, scrollable frames expand, text output panes expand, and all inner content fills the available space without clipping.
- Clicking the green maximize button (macOS) or double-clicking the title bar fills the screen, and all widgets stretch to fill the larger area.
- Restoring from maximize returns to the previous size, and the layout reflows accordingly.
- All existing functionality (button clicks, command execution, progress display, file selection) continues to work identically β€” only the layout responsiveness changes.
- No new buttons, menus, or settings for layout control are introduced.
- Fixed-width constraints that would prevent proportional growth are removed from all growable widgets.

VERIFY:
```sh
cd /Users/renan/development/ps5/psbrew/mkpfs && python -c "
import tkinter as tk
import customtkinter as ctk
import ast, sys

# Check 1: geometry() and minsize() are called in app.py
with open('mkpfs/gui/app.py') as f:
app_tree = ast.parse(f.read())
found_geo = False
found_min = False
for node in ast.walk(app_tree):
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
if node.func.attr == 'geometry':
found_geo = True
if node.func.attr == 'minsize':
found_min = True
if not found_geo:
print('FAIL: app.geometry() not found in app.py')
sys.exit(1)
if not found_min:
print('FAIL: app.minsize() not found in app.py')
sys.exit(1)
print('OK: geometry() and minsize() found')

# Check 2: run project's test suite if available, to verify no existing functionality broke
import subprocess
result = subprocess.run([sys.executable, '-m', 'pytest', '--co', '-q', 'tests/'], capture_output=True, text=True, timeout=30)
print('pytest collection:', result.stdout.strip() if result.stdout else 'no tests found')
if result.returncode not in (0, 5): # 5 = no tests collected
print('WARN: pytest exited with code', result.returncode, result.stderr)
"
```

## phase timings

refine 27.3s
research 7158.3s
worker:files wait 541ms
worker:files work 21.8s
worker:apis wait 890ms
worker:apis work 678.1s
worker:context wait 867ms
worker:context work 39.8s
worker:tooling wait 665ms
worker:tooling work 3.6s
workers 5109.8s
verify-tooling 2048.5s
grill 1.5s
gen 1.5s
compose 7128.0s
critique 1907.8s
triage 1859.3s
rewrite 48.5s
total 16222.9s

## handoff

handoff_at: 2026-07-03T07:37:29.871Z
52 changes: 52 additions & 0 deletions .pi-tasks/TASK_AUTO_0001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
id: TASK_AUTO_0001
state: completed
phase: done
created_at: 2026-07-01T14:06:42.651Z
updated_at: 2026-07-03T10:16:08.605Z
title: I've recently implemented a simple GUI to allow some users to run the mkpfs commands without having to use the command line directly.
---

## feature prompt

I've recently implemented a simple GUI to allow some users to run the mkpfs commands without having to use the command line directly.

The problem is that the GUI is outdated: it does not use the latest commands and it is missing many options for each subcommand.

I want you to refactor the GUI so it reflects all the subcommands we have, and add an "Advanced settings" area that displays settings based on the parameters available for each
subcommand.

Some subcommands (for example, pack folder) have different modes (e.g. --raw (folder -> FFPFS) and the mode that wraps Fodler -> exFAT -> FFPFSC). These should be presented as primary options.

Each main command should be selectable from the left-side navigation (pack file, pack folder, tree, verify, exfat, etc.).

Also, the gui module is currently too complex and implemented as a single file. Instead of using gui.py, create a GUI package (a module folder) and split the code into 3–6 modules that group related parts logically. The goal is to make the GUI easy to maintain and simple to update when new commands or options are added.

One small subtask: show the correct mkpfs version in the footer (it currently appears to be hardcoded to 1.0.0). Also, when an input file is selected, if the output field in the
same form is empty, default the output path to the same folder as the input and prefill a sensible output filename and extension based on the selected operation.

Another subtask: update the GUI so it captures and displays CLI progress bars. The GUI should present the progress both in the shell output area and as a native GUI progress bar
with a status label that mirrors the shell progress (percentage and phase text).

Finally, the window must be resizable and maximizable, and the layout should adapt to different screen sizes. You may enforce a reasonable minimum size, but users must be able to
maximize the window.

In the end, I want a polished, OS-independent UI that supports all mkpfs commands and options, is easy to use, and looks professional.

## clarifications

Q1: Should the refactor keep the existing CustomTkinter/tkinter UI stack, or should we migrate the GUI to a different toolkit (e.g., PySide6/PyQt) before splitting work? This choice changes dependencies, packaging, available widgets (native vs themed), how progress bars and subprocess output are integrated, and therefore which implementation tasks and modules are needed first.
A1: keep CustomTkinter/tkinter to minimize scope, preserve current behavior and packaging, and focus tasks on modularization, command/option mapping, progress parsing, and responsive layout.
Q2: Should the GUI discover available subcommands/options at runtime (by importing the mkpfs package or parsing mkpfs --help) so the UI is generated dynamically, or should it be driven from a static declarative schema file in the repo that must be updated when commands change? This choice determines whether we need runtime introspection and help-parsing/proc-handling tasks (and fallbacks) versus tasks to design, validate, and maintain a single authoritative UI schema.
A2: discover commands/options dynamically by first importing the mkpfs package to read its command/option metadata and fall back to parsing mkpfs --help if import metadata isn't available, so the GUI stays in sync automatically.
Q3: May this refactor add a small shared GUI-support API inside mkpfs itself for command metadata, default output naming, and progress events, or must the new GUI stay a pure consumer of the existing CLI/help text without changing core modules? This most changes the split because it decides whether we first land reusable core adapters/hooks or instead build brittle GUI-side parsers around argparse help and terminal output.
A3: allow a small shared API in mkpfs (reusing the existing argparse builders and pbar.Progress) so the GUI package stays thin and future command changes only need core updates in one place

## tasks

- [x] TASK_0001 Add shared mkpfs metadata and progress hooks | decisions (explicit user choices β€” these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events; discover commands/options dynamically by first importing the mkpfs package and fall back to parsing mkpfs --help if import metadata isn't available
- [x] TASK_0002 Split the GUI into a package with focused modules | decisions (explicit user choices β€” these OVERRIDE the spec doc wherever they conflict; follow them exactly): keep CustomTkinter/tkinter to minimize scope, preserve current behavior and packaging, and focus on modularization
- [x] TASK_0003 Build dynamic command navigation and option forms | decisions (explicit user choices β€” these OVERRIDE the spec doc wherever they conflict; follow them exactly): discover commands/options dynamically by first importing the mkpfs package and fall back to parsing mkpfs --help if import metadata isn't available; allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events; keep CustomTkinter/tkinter to minimize scope
- [x] TASK_0004 Implement default output naming, footer versioning, and progress mirroring | decisions (explicit user choices β€” these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events
- [x] TASK_0005 Make the window responsive, resizable, and maximizable | decisions (explicit user choices β€” these OVERRIDE the spec doc wherever they conflict; follow them exactly): keep CustomTkinter/tkinter to minimize scope, preserve current behavior and packaging

11 changes: 11 additions & 0 deletions mkpfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,15 @@
MkPFS is a toolkit for building, verifying, browsing, and managing PlayStation PFS images.
"""

from .discovery import (
ProgressEvent,
cli_metadata,
default_image_basename,
default_output_name,
get_cli_metadata,
normalize_output_path,
register_progress_handler,
)
from .pbar import Progress

__version__: str = "1.0.0"
Loading
Loading