Skip to content

Release/v0.4.0 - #22

Merged
Addi90 merged 55 commits into
masterfrom
release/v0.4.0
Aug 30, 2026
Merged

Addi90 merged 55 commits into
masterfrom
release/v0.4.0

Conversation

@Addi90

@Addi90 Addi90 commented Aug 30, 2026

Copy link
Copy Markdown
Owner

release of v0.4.0

Addi90 and others added 30 commits August 22, 2026 23:21
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.
.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.
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.
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.
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.
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.
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.
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.
cmd_test now runs pytest (engine + API) and then 'npm run test' in
frontend/ (35 vitest tests); help text updated to reflect both suites.
…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.
… workflow (3-OS PyInstaller, smoke test, GitHub Release)
…-and-release

Feat/GitHub actions for development and release
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.
…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.
- 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.
…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: §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.
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.
- 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.
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'
Addi90 and others added 25 commits August 27, 2026 21:32
…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
…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.
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.
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
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.
/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.
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 Windows artifact name (-win64.zip -> -windows.zip, matches build/build.py)
- CONSOLE=False is unconditional in arenamer.spec (GUI app; macOS console=True
  would hide the Dock icon)
- release.yml fires on the closed/merged PR event (fix 1089079), not a direct
  PR run; notes fetch-depth: 0, optional RELEASE_PAT, push master --tags
- pyproject.toml is pip-installable (pip install -e . -> arenamer)
- note plan.md as historical (directory renaming shipped in 0.3.0)
- App.svelte error banner is dismissible; httpx2 in dev requirements
docs: align AGENTS.md and README with v0.3.0
Port the approved prototype design into the real app:

- Token system in index.css (dark + light): 4-level surfaces, 3-level
  borders, 3-level text, radius/scale tokens, named shadow levels, scrim
- Dark/light theme switcher (topbar) + pre-paint script in index.html
  (no FOUC), persisted in localStorage
- Breadcrumb path bar replaces Home/Up (crumb click navigates, current
  crumb bold); pill language select, icon theme button
- Modifier cards: numbered steps (01–07), enable dot, one-click
  reset-mini, drag reorder preserved; modifiers reduced to controls only
- File list: independent Files/Directories chips, mono name + muted new
  name, success-green changed names with dot, danger-tinted duplicate
  rows, sticky thead, dir badge
- Dialog + RenameButton (pill count) rebuilt on the system

Contrast: every pair in both themes reaches APCA Lc ≥75 at its used
size except the dark --faint metadata tier (APCA 36, WCAG 4.4) —
documented in DESIGN.md (raising it to 75 would flatten it into
--muted and kill the 3-tier hierarchy).

All 48 frontend + 145 backend tests pass; DESIGN.md (Google Labs
format) committed as the system anchor.
The 34px check cell had 12px side padding (10px content box), so the
18px checkbox overflowed it. Chrome's text-overflow: ellipsis on the
cell then paints a "…" right next to the control (and clips it).
Zero the horizontal padding on .col-check so nothing overflows.
feat: modern UI — design tokens, dark mode, APCA-tuned contrast
Remove dead code and hedged assertions that let implementations drift:
- drop ghost "state.path" writes (no such property; guards key off currentPath)
- drop the 250ms waits (the debounce lives in App.svelte's $effect, not in
  the store functions the tests call directly; a 0ms flush suffices)
- assert the /preview payload strictly (names as strings, sanitized config)
- assert duplicateNames as a plain array (the store assigns res.names)
- exercise interpolation with a real key (rename.confirmMsg) instead of a
  fake key that only works via the unknown-key fallback
Adds the two test files identified in the suite review and expands the
store/component smoke tests:

- lib/api.test.js: response parsing + error mapping (10 tests),
  including the new {path, dirs} payload contract
- modifiers smoke: all seven panels (toggle class, bindings,
  conditional inputs, grey-out) as a second component layer (21 tests)
- store: goUp bounds, refreshPreview guards, checkDuplicates,
  performRename happy + 409 paths, dialog resolution semantics
  (13 -> 27 tests)
- components smoke: RenameButton dialog paths mount the Dialog host
  alongside; ModifierCard gets a proper (raw) snippet as children and
  real DataTransfer drag events

Frontend suite: 48 -> 98 tests, all passing. Svelte 5 flushes DOM
updates on a later tick, so DOM assertions follow an explicit settle.
The panels greyed out via CSS only (opacity + pointer-events: none),
which blocks the mouse but not the keyboard: users could Tab into a
"disabled" panel and edit its config, and assistive tech reported
the controls as enabled. Wrap the controls in <fieldset disabled> —
the attribute cascades a real disabled state to every input and
select — and keep the opacity as the visual cue (dropping
pointer-events).

Remove's range inputs already used real disabled attributes for their
sub-option gating and compose with the fieldset unchanged; the
sub-option split becomes a stated rule in AGENTS.md: mode-only
inputs render conditionally ({#if}), persistent-value inputs
disable via attribute.

Tests now assert the fieldset's disabled attribute (a strictly
stronger check than the old class check).
The Range checkbox shared its row with the from/to values and the
until-end checkbox, which read as one crowded strip. Give the toggle
its own row and let the range values (from, to, until end) group on
the row below — the only row that still dims while range is off (the
toggle itself no longer greys out, since it is the control that turns
the section back on). DOM input order is unchanged, so the existing
tests need no selector updates.
- AGENTS.md: DESIGN.md + index.css in repo layout; App.svelte line now
  reflects theme toggle and breadcrumb path bar; new theming bullet in
  frontend essentials; dark mode no longer an open item
- README.md: mention the light/dark theme toggle; DESIGN.md in layout
- DESIGN.md: point token reference at frontend/src/index.css (the
  prototype lives on a separate branch)
- drop superseded plan.md (directory renaming shipped in 0.3.0)
Frontend test hardening + docs sync (rebased onto develop)
- RenameButton: move the light-theme .pill override to index.css (global).
  An html-attribute ancestor inside scoped component CSS trips
  svelte's css_unused_selector check; the rule is unchanged.
- ModifierCard: the drag grip is now purely decorative (aria-hidden,
  no tabindex/role). Dragging is mouse-only; the accessible controls
  are step-head (toggle) and reset-mini (reset). Removes the
  a11y_click_events_have_key_events warning and a dead tab stop.
@Addi90
Addi90 merged commit 596b4ef into master Aug 30, 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