Release/v0.3.0 - #18
Merged
Merged
Conversation
Add vitest, @testing-library/svelte, happy-dom as dev deps plus test/test:watch scripts and a separate vitest.config.js (Svelte plugin, happy-dom, src/**/*.test.js). Includes a sanity test for defaultConfig to prove the toolchain runs; real coverage lands in later milestones.
Directories become first-class rename entries:
- RenameFile gains is_dir; for directories the whole name is the base
and ext is always "" (a dot in a dir name is part of the name, so
backup.tar renames to x_backup.tar). set_name keeps the entry
extension-less after a rename.
- build_files(path, names, dirs=None) marks the named subset as
directories; callers without dirs keep the historical behavior.
- preview() items gain type ("file" | "dir").
- Duplicate detection is cross-type by design (os.path.exists covers
dir->existing-file, dir->existing-dir, file->existing-dir).
- perform_rename works on directories via os.rename (content preserved);
errors are still collected per entry.
All seven modifiers work unchanged since they operate on base.
Numbering follows the combined on-screen list order across mixed
file+dir selections. +20 engine tests (126 total), all green.
config.test.js: defaultConfig shape/freshness plus sanitizeConfig coercion (null -> 0, string numbers, garbage, clamping, non-numeric passthrough, no input mutation). languages.test.js: en/de identical key sets, non-empty values, locale/saved-choice detection, t() translation and {var} interpolation, raw-key fallback, setLanguage persistence.
…ename
- GET /api/list now returns files *and* subdirectories; every entry
carries type ("file" | "dir"), dirs report size 0. The UI filters
the two types with view toggles (default: files shown, dirs hidden),
so the default view matches the historical behavior.
- /api/preview, /api/check, /api/rename accept an optional dirs list
(names in files that are directories); omitted dirs keeps the
historical all-files behavior (backward compatible).
- Preview items report type; duplicate detection is cross-type
(dir->existing-file, dir->existing-dir, file->existing-dir all
detected, /rename refuses with 409).
+7 API tests (133 total), all green.
store.svelte.test.js: selection (toggleSelect/selectAll/clearSelection), pipeline order (canonical default, reorderModifier permutation, resetModifierOrder), showDialog (open flag, fields, info default) and api-backed flows with vi.mock of lib/api.js (loadDir happy + error, refreshPreview request payload, checkDuplicates, performRename). Fresh module per test via resetModules + dynamic import.
- New store state: showFiles (default true), showDirs (default false), treeVersion (bumped after renames so the tree can refresh labels). Toggles are view state, not modifier config (defaultConfig unchanged). - selectAll only selects visible entries; hiding a type prunes it from the selection, previews and duplicate highlights, so hidden entries can never be renamed invisibly. - preview/check/rename payloads now include dirs (the selected names that are directories), matching the new API contract. - i18n: fileList.toggleFiles/toggleDirs added; dialog copy generalized File(s)->Item(s) (de: Datei(en)->Element(e)); app subtitle updated. +8 store tests (43 frontend total), i18n key parity holds.
components.smoke.test.js renders the core components against the real module-level store: FileList rows + click-to-select, Dialog open/close + button dismiss, RenameButton check->confirm flow and blocking duplicate warning (mocked api). The file opts into jsdom (happy-dom crashes on Svelte 5 checkbox mounting), and vitest.config.js aliases bare 'svelte' to its client entry so mount() resolves under Vitest's SSR-style transform.
The drag-over highlight outlined the whole target card, leaving it ambiguous whether the dragged card would slot in before or after it. Hovering now shows an insertion line in the gap before or after the target card (chosen by pointer position relative to the card's midpoint; vertical edges on the narrow-screen grid), and reorderModifier takes an insertion slot instead of a card index, so the drop does exactly what the marker shows.
…fresh - FileList: Files/Directories checkboxes in the toolbar (default: files only, the historical view); rows render only visible entries; dir rows get a small type badge; header checkbox and select-all key off the visible set so hidden entries are never selected. - DirectoryTree: watch store.treeVersion and re-fetch the children of every loaded node, so renamed directories get fresh labels. - RenameButton: bump treeVersion after a successful rename. - i18n: fileList.typeDir (en "dir" / de "Ordner"). +3 smoke tests (46 frontend total).
AGENTS.md: vitest suite overview (35 tests, four files), toolchain notes (client-entry alias, jsdom for component smoke tests), and 'local only, no CI' stance; layout line now lists vitest.config.js.
.modifiers is the scroll container (overflow-y: auto), which clips the insertion line drawn above the first card (and the vertical markers at the left edge on narrow screens). Add padding so the edge markers fit inside the scroll area.
- AGENTS.md: §1 files and directories; §2 extension-less dir rule; §3 toggles/badges/cross-type safety; §4 new behavior decisions (directory renaming ADDED, view toggles are view state not config); §5 typed /list, dirs payload, store state, refreshed test counts (116/17); milestone 10. - README.md: intro + safety paragraph mention directories and toggles. - plan.md: final execution log with commit SHAs and deviations.
… workflow (3-OS PyInstaller, smoke test, GitHub Release)
cmd_test now runs pytest (engine + API) and then 'npm run test' in frontend/ (35 vitest tests); help text updated to reflect both suites.
Adds a small button in the modifier pane header that restores the canonical pipeline order. It only appears while the current order differs from the default (orderChanged $derived), and resetting re-runs the live preview via the existing config $effect. Also fixes the drag indicator on very narrow screens: 'side by side' now compares the card width to the container instead of the grid display, so stacked (single-column) cards still get horizontal above/below markers.
A leading dot (index 0) no longer starts the extension — the os.path.splitext convention. Previously ".bashrc" split into base="" + ext=".bashrc", and since every modifier operates on the base, dot-files (hidden files on Linux/macOS) could never be renamed at all (Replace/Case were no-ops; Add suffix inserted before the name text; Remove could never strip the dot). - split_name: a dot at index 0 is not an extension delimiter; ".hidden.tar" still splits at its last real dot (base ".hidden"). - +10 tests (TestDotFiles: replace/case/add/remove/counting/ifthen/ preview/perform_rename/duplicate-check on dot-files, incl. an on-disk .bashrc -> .zshrc rename); 2 old tests updated to the new convention. - AGENTS.md: §2 split rule, §4 FIXED entry, engine test count, milestone 11.
…ated tests/test_engine.py + test_api.py -> tests/backend/ (pytest from repo root still finds them; ./do test covers both suites). A shared tests/frontend/ folder was tried and abandoned: Vite refuses to load test files outside its project root even with root lifted to the repo root, so the vitest suite stays under frontend/src/ (vitest convention). AGENTS.md layout + test docs updated to match.
Feat/flexible modifier order
…vetica 'A') - public/favicon.svg: purple gradient folder, all corners rounded, bold white 'A' drawn from the real Helvetica Bold glyph outline (pixel-identical on every OS, no font dependency) - public/favicon-32.png + apple-touch-icon.png: rasterized compatibility variants - index.html: icon links (SVG primary, PNG fallbacks) - App.svelte: show the logo in the header next to the title
- card <div> (has drag handlers) gets role="listitem"; the modifiers <section> container becomes role="list" (listitem needs a list parent) - the grip (role="button") gets tabindex=0 so it is keyboard-focusable Silences vite-plugin-svelte's a11y_no_static_element_interactions and a11y_interactive_supports_focus warnings; the production build is now clean. Reordering by keyboard is a possible follow-up.
Feat/frontend tests
…ndows exe)
- build/make_icons.py: rasterize frontend/public/favicon.svg into
build/icons/{arenamer.png,arenamer.ico,arenamer.icns}; best effort - keeps
the committed icons when cairosvg/Pillow are unavailable
- build/icons/: the generated icon assets (committed so any OS can build)
- build/arenamer.spec: BUNDLE icon for the macOS .app, EXE icon on Windows,
build/icons bundled for the runtime window icon, version wired into
CFBundleShortVersionString; CONSOLE=False - a GUI app (console=True forces
LSBackgroundOnly=True, which hid the Dock icon entirely)
- build/smoke.spec: exe icon on Windows
- backend/main.py: pass the icon via webview.start(icon=) - pywebview 6.x
moved the icon parameter out of create_window (GTK/QT; on macOS/Windows
the window icon comes from the .app/exe icon)
- build/build.py: icon (re)generation step + --no-icons flag
Verified: .app plist references arenamer.icns (0.2.0, Retina, Dock icon),
frozen app launches cleanly, headless smoke test passes.
Stale fixes (verified against the code): - layout tree: add build/ (packaging sources + gitignored output), pyproject.toml, requirements-build.txt, ModifierCard.svelte; drop the meaningless '(done)' marker - store action list: add reorderModifier / resetModifierOrder - frontend architecture: add a ModifierCard bullet (DnD reorder, insertion-line, role=list/listitem) - store test description: mention view toggles - milestone 8: drag-and-drop, empty states and error handling are done (ModifierCard, store.error, empty rows) — only dark mode + keyboard shortcuts remain; mark partial instead of 'next' - §3 intro: 'UI wiring lands in later milestones' is no longer true Filler removed: - the end-of-§5 blockquote repeating the §4 'preview shows the full name' decision - the 'For a full desktop run' paragraph duplicating the backend section and §1's ':8000 fallback'
…-and-release Feat/GitHub actions for development and release
The treeVersion $effect in DirectoryTree ran refreshLoaded() inside the effect body, so Svelte tracked every deep node read (children/loaded/ loading). Each refresh re-assigns children and toggles loading, which invalidated the effect again: an endless re-fetch loop that replaced child nodes with fresh collapsed objects. In the app the tree therefore collapsed to its first level as soon as you expanded anything (you could enter adrian via the file list, but repos never appeared); in tests the loop starved the event loop into a hang, making the tree untestable. The effect now tracks only treeVersion (guarded by lastTreeVersion) and runs the refresh in a queueMicrotask outside the effect body, so the deep reads are untracked. refreshLoaded also preserves a node object (expansion, loaded flag, subtree) for entries whose name survives a rename, so label refreshes no longer collapse the user's view. Adds DirectoryTree.test.js: expanding to depth 3 persists after settling, and a treeVersion bump refreshes labels while surviving expansions stay.
The subtitle sat on the vertical center of the title's tall line box, which made it float near the top of the big text. Baseline-align the title group so it sits anchored next to the bottom of the title text; drop the old h1 margin hack and pin line-height for stable metrics.
Feat/folder editing
/list and /dirs called os.listdir unguarded, so a directory that exists but the app may not read (e.g. macOS TCC blocking an external volume) crashed with a raw PermissionError traceback and an opaque '500 Internal Server Error' in the UI. Both endpoints now go through _list_dir(), which translates PermissionError into a 403 naming the path and, on macOS, pointing to System Settings > Privacy & Security > Full Disk Access; other OSErrors become a 500 naming the path. The rest of the stack was already safe (date modifier falls back, duplicate check uses os.path.exists, perform_rename collects per-entry errors). Adds test_unreadable_dir_403_with_friendly_detail.
make_icons.py is best-effort by design, but its guard only caught ImportError. When cairosvg is installed yet the native libcairo is not on the dynamic linker's default search path (e.g. Homebrew cairo with python.org's Python), cairocffi raises OSError and the whole build died. - catch the native-lib case separately: find the cairo lib in the usual locations and re-exec the script once with it on DYLD_FALLBACK_LIBRARY_PATH / LD_LIBRARY_PATH (dyld is read at process start, and matches loaded libs by absolute path, so preloading cannot work); guard against re-exec loops - if neither the package nor the native lib is available, warn and keep the committed build/icons/ — the build succeeds - pin cairosvg + pillow in requirements-build.txt so fresh clones/CI can regenerate icons - CI (build.yml): install libcairo2 on the Linux job so the regeneration path actually runs there instead of silently falling back
Feat/add logos and favicon
Adds a round x-in-circle button on the right of the error banner (clearError in the store, i18n app.errorDismiss in en/de). The existing behavior is kept: the banner also clears itself on the next loadDir.
fix(build): never fail the build on icon (re)generation
Fix/tree depth and permissions
Release/v0.3.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.