Skip to content

Release/v0.3.0 - #18

Merged
Addi90 merged 47 commits into
developfrom
release/v0.3.0
Aug 28, 2026
Merged

Addi90 merged 47 commits into
developfrom
release/v0.3.0

Conversation

@Addi90

@Addi90 Addi90 commented Aug 28, 2026

Copy link
Copy Markdown
Owner

No description provided.

Addi90 and others added 30 commits August 22, 2026 23:19
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.
…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.
Addi90 and others added 17 commits August 28, 2026 18:00
…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.
/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
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
@Addi90
Addi90 merged commit c3a43b8 into develop Aug 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant