Skip to content

v0.0.1-beta

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 May 04:35
· 33 commits to main since this release

Release Notes — v0.0.1-beta

한국어 버전 / Korean version

Date 2026-05-14
Range 7cd5272 .. 2723fa6
Commits 37 individual / 12 merge commits
Components Backend (Go + SQLite), Frontend (SvelteKit + Svelte 5 + Tailwind 4)
Verification Type-check, production build, backend integration tests, end-to-end browser tests

First formal release. Six new features were merged into main, six user-reported issues were resolved in a single batch, and the UI tone (typography, spacing, triggers) was retuned.


1. Features

1.1 Folder Upload Hardening

Both code paths — OS folder drag/drop and the Upload folder picker — are hardened to the same level.

  • Recursion depth capped at 32. Exceeding the limit emits a one-shot warning toast and preserves the partial upload.
  • Dot-prefixed segments (.git, .DS_Store, intermediate .hidden/, etc.) are skipped at every path depth.
  • FileSystemDirectoryReader.readEntries is wrapped in try/catch — partial read failures preserve the already-collected files and continue.
  • Drop completion surfaces Queued N file(s) for upload or No uploadable files in dropped folder.
  • window.__nasTestEnqueueFolder DEV-only harness exposed; the import.meta.env.DEV guard tree-shakes it from production builds.
  • Backend regression test added (TestTusUploadCreatesNestedFolders) covering 3-level / 5-level nested and root-level uploads — verifies os.MkdirAll(0o755) behaviour.

1.2 File Details (Size, Modified, Inspector)

  • Backend fileEntry JSON gains size: int64 and modifiedAt: RFC3339. Populated via a single entry.Info() call — no additional stat round-trips.
  • List view: Size column (right-aligned, two-decimal precision, — for folders) and Modified column (just now / Nm / Nh / Nd / Mon D, YYYY). The Modified cell carries a title attribute with the full timestamp.
  • Grid view: file tile title becomes "name\n1.00 MB · 2h".
  • Inspector side panel (w-[360px], mirroring the UploadPanel pattern): icon + name / Type / Size / Modified / Path. Trigger behaviour is described in §3.3.

1.3 Activity Graph & Git-style Timeline

  • aggregate.ts module: dailyCounts(entries, days=30) (local-date buckets, zero-filled) and typeDistribution(entries) (OPEN→VIEW fold, ratio-guarded).
  • ActivityGraph.svelte banner: 30-day sparkline (accent yellow #fabd2f, height 48, ceiling = local max) + distribution stacked bar + legend pills (title="N · X%").
  • The timeline is grouped under sticky day-section headers (Today / 2026-05-14 / 10 events). Each row is a three-column layout: HH:MM mono · activity dot + author-initial chip · activity type + [TARGET] chip + author name + path.
  • MOVE entries are decomposed by a parse() helper into /from → /to with a lucide ArrowRight between them. UPLOAD / CREATE / DELETE render as a single mono path.
  • Side fix: ActivityLog.load() silently dropped responses shaped as {data: [...]} — corrected as part of the same change.

1.4 Music and Video Library Tabs

  • Backend route GET /mediaLibrary?kind=audio|video added. Walks the NAS via filepath.WalkDir with fs.SkipDir on dot-prefixed directories. When the result count reaches MEDIA_LIB_LIMIT (default 5000) the walk exits early and the response carries X-Library-Truncated: true.
  • The mediaEntry payload separates loc (directory) and name (filename), matching the existing FilePayload shape — the frontend can hand it straight to a viewer tab without splitting.
  • MusicLibrary.svelte: compact list grouped by entry.loc + search filter + IntersectionObserver pagination (200 initial + 200 per sentinel).
  • VideoLibrary.svelte: 16:9 grid (auto-fill, min 220px), film icon placeholders, IntersectionObserver pagination (60 + 60).
  • Clicking a row or tile opens the existing MediaViewer tab. The KIND_TO_ICON maps in the sidebar, TabBar, and QuickOpen are updated to cover the new kinds.

1.5 Mobile UI Support

Project-wide responsive conversion. The existing ui.svelte.ts isMobile / breakpoint / viewportWidth / viewportHeight fields are reused; the store is extended only with drawerOpen.

  • .h-app utility (100vh fallback + 100dvh override) — avoids iOS Safari URL-bar clipping.
  • (app)/+layout.svelte grid: grid-cols-[1fr] md:grid-cols-[auto_1fr]. The sidebar is wrapped in hidden md:contents so it leaves the DOM flow on mobile.
  • New MobileDrawer.svelte: fixed inset-0 z-50 backdrop + left-anchored w-64 panel. Body scroll is locked via documentElement.style.overflow = 'hidden'. Esc and md+ resize both auto-close.
  • Header.svelte mobile condense: hamburger button (menu icon, md:hidden); logo, username, and Quick-open label are hidden md:flex.
  • Toolbar.svelte + new ToolbarOverflow.svelte: only Up / Upload (icon) / View toggle stay visible on mobile; Upload folder, New folder, and inline search collapse into a ... overflow dropdown. The search input is reached via a dedicated toggle that reveals a full-width bar.
  • TabBar.svelte: mobile-only width shrink (min-w-[100px] max-w-[140px]), always-visible close X (md:opacity-0 md:group-hover:opacity-100), and draggable={!ui.isMobile} to disable reorder.
  • UploadPanel.svelte and Inspector.svelte become bottom sheets on mobile (fixed inset-x-0 bottom-0 max-h-[70dvh] rounded-t-lg pb-[env(safe-area-inset-bottom)]).
  • QuickOpen.svelte goes fullscreen on mobile. A visualViewport.height listener keeps the search input above the soft keyboard.
  • Touch input: new long-press.ts Svelte action (500 ms hold, cancels on >8 px movement) unifies the context-menu invocation path. Entries set draggable={!ui.isMobile} to disable HTML5 drag on touch devices.
  • Viewer responsiveness: MonacoViewer sets wordWrap to on on mobile. VideoControls and PdfViewer secondary controls collapse behind a ... overflow.
  • During code review, the same outside-click pattern was found duplicated across three components; it was extracted into frontend/src/lib/actions/click-outside.ts.

1.6 Marquee and Multi-Select

Per-tab isolated multi-selection, marquee rectangle selection, and batch operations.

  • Selection state lives in ExplorerPayload.selection?: string[] (the last item is the Shift range anchor).
  • Click modifier precedence: Shift > Ctrl/Cmd > none. setSingle / toggleEntry / selectRange / clear are pure functions in frontend/src/lib/components/Explorer/selection.ts.
  • Keyboard: Ctrl/Cmd+A (select all), Esc (clear), Delete (batch delete via Promise.allSettled, then a Deleted N (M failed) summary toast). All shortcuts short-circuit when the focused element is editable.
  • Marquee: pointer-down/move/up handlers with a three-layer guard (data-marquee-canvas ancestor check, scrollbar-offset rejection, editable-target rejection), 32 px edge auto-scroll at 8 px/frame with entry-rect re-snapshot, and a 4 px dead zone separating plain clicks from drags.
  • Drag payload uses a batch shape {items: Array<{name, isFolder}>, sourceLoc}. Legacy single-entry payloads are wrapped automatically by readPayload (back-compat).
  • Mobile selection mode: long-press to enter → tap toggles → top bar with N selected + Cancel (X) + Delete (trash).

2. Fixes

2.1 User Feedback Batch

Five reported issues plus one discovered during cleanup were resolved together.

# Issue Root cause Fix
1 Search bar autofilled the login ID Browser autofill heuristics All four search inputs received type="search" + name="*-q" + autocomplete="off" + autocorrect="off" + spellcheck="false"
2 .avi / .mkv / .mov streaming failed Backend mime fell back to video/mp4; browsers cannot decode the containers natively Added video/x-msvideo, video/x-matroska, video/quicktime mappings to contentTypeFor. VideoPlayer got an <video onerror> handler that overlays "Browser cannot play this format" with a Download button
3 Global search did not work QuickOpen filtered open tabs only Added a /server/searchInAllFiles call — 180 ms debounce + monotonic token cancellation. Tab hits and file hits are sectioned separately; clicking a file opens the Explorer or a viewer tab
4 Stale entries after rapid post-mount navigation Mount-fetch resolved after navigate-fetch and overwrote the newer state Explorer.refresh() now uses a monotonic refreshToken — newer in-flight calls invalidate older responses
5 Folder rename did nothing A reactivity loop blocked refresh, leaving the UI stale Resolved as a side effect of §2.2. Verified with renameTest → renamedFolder end-to-end plus filesystem check
6 (Discovered) Search returned loc: "/." for files at the NAS root filepath.Rel(baseDir, dir) returns . for the root parent Normalized "/." → "/"

2.2 Explorer Reactivity Loop

Symptom: after page load the "Loading…" indicator never cleared, and the console emitted effect_update_depth_exceeded. Delete and rename actions also failed to refresh entries.

Cause: a $effect introduced with the multi-select work wrote to tabs.list via updateSelection. TabContent then re-evaluated <Explorer loc={tab.payload.loc}>; Svelte 5 treated the prop re-evaluation as a change, the effect re-fired, and the loop never converged.

Fix:

  • Inspector selected moved from $state + $effect mirror to a pure $derived (write path removed).
  • The loc-change $effect no longer calls updateSelection([]). Navigation handlers (navigateTo, etc.) set payload.selection = [] explicitly.
  • The title-update tabs.update call is wrapped in untrack().

After this fix, folder rename and delete refresh automatically as intended.


3. User-Interface Refinements

3.1 Typography — IBM Plex Sans, Weight 550 Floor

  • Latin variable font (@fontsource-variable/ibm-plex-sans, axis 100–700) plus Korean static font (@fontsource/ibm-plex-sans-kr, weights 500/600/700).
  • --font-sans: "IBM Plex Sans Variable", "IBM Plex Sans KR", "Inter", ui-sans-serif, ....
  • html default weight set to 550 (font-variation-settings: 'wght' 550). Korean text snaps to the nearest static weight, 600.
  • Tailwind weight helpers remapped: font-medium → 600, font-semibold → 650, font-bold → 700. The 400–500 range is no longer used; 550 is the new floor.
  • font-mono, code, pre, kbd, samp keep FiraD2 with font-variation-settings: normal since the mono is a static font.

3.2 Background Context Menu

Right-clicking the file canvas blank space now opens a folder-level menu.

  • Entries: New folder · Upload files · Upload folder · Refresh.
  • Reuses the marquee three-layer guard — never fires over an entry or an editable input.
  • Mutually exclusive with the entry context menu (bgMenuOpen ↔ menuOpen).

3.3 Inspector — Explicit Trigger

Previously a single click opened the Inspector and obscured the file area. The Inspector now responds only to explicit selection.

  • Single click: applies the selection ring (bg-bg-elevated ring-1 ring-accent) only. The Inspector does not open.
  • Right-click → Details (lucide Info icon) → Inspector opens.
  • While the Inspector is open, clicking a different entry does not swap its target — only the explicit close (X) or a folder navigation closes it.
  • If the inspected entry disappears (deleted, renamed, filtered out), $derived inspectedExists plus an $effect wrapped in untrack close the Inspector automatically without creating a write-back loop.

4. Infrastructure and Refactors

  • Dead-code removal: viewerIconName() in registry.ts and unused methods/state on FilesStore.
  • Drag-drop handler duplication between FileGrid and FileList consolidated into the dragEntry Svelte 5 action.
  • click-outside.ts action extracted: ToolbarOverflow, VideoControls, and PdfViewer shared the same outside-click pattern; consolidated into a single action (caught during code review).
  • long-press.ts action introduced: a shared entry point for mobile context-menu and selection-mode triggers.
  • Docker PORT env consistency: the Dockerfile HEALTHCHECK (shell-form) and docker-compose.yml internal container port binding both honour $PORT. A single env value now applies across the full deployment chain.
  • Temporary-data cleanup: gitignore patterns at repo root and backend/ were unified, 12 previously-tracked TUS scratch files were untracked, and tmp/README.md was added as a guide. Tool cache directories that write to fixed paths (data/nas/*, data/tmp/, etc.) are gitignored separately.

5. Verification Summary

Pre-merge static checks, production build, backend integration tests, and end-to-end browser scenarios all passed. The six user-reported issues were individually verified via browser checks and DOM probes.

End-to-end scenario coverage (per-feature step counts):

Feature Scenarios
Folder upload 7
File details 9
Activity graph 8
Media libraries 8
Mobile UI 18
Marquee + multi-select 21

6. Known Limitations and Follow-ups

The following items are out of scope for this release and tracked as follow-ups.

  • Notifications Toaster not implemented: notifications.svelte.ts fills a queue, but no component renders it — every toast is currently silent. Slated for a separate PR.
  • iOS Safari device verification: safe-area-inset-bottom and visualViewport keyboard tracking cannot be fully emulated by desktop browser automation. Real-device verification deferred.
  • Decomposition of Explorer.svelte: the single component has grown to 637 lines. Splitting the marquee / shortcuts / selection / drag-move concerns into four composables is tracked in the backlog.

Appendix — Modified Files

Backend

backend/
  internal/config/config.go           — MediaLibraryLimit field + MEDIA_LIB_LIMIT env loader
  internal/files/handlers.go          — fileEntry { size, modifiedAt } / MediaLibrary handler / Search loc normalization / Rename
  internal/server/router.go           — /mediaLibrary route
  internal/stream/handlers.go         — .avi / .mkv / .mov mime mappings
  tests/integration/files_test.go     — TestReadFolderDetails + TestMediaLibrary
  tests/integration/tus_e2e_test.go   — TestTusUploadCreatesNestedFolders
Dockerfile / docker-compose.yml       — PORT env consistency

Frontend

frontend/src/
  app.css                             — IBM Plex Sans + 550 floor + .h-app utility + Gruvbox tokens
  lib/store/ui.svelte.ts              — drawerOpen + body-scroll-lock + resize auto-close
  lib/types.ts                        — TabKind music-library/video-library + MediaEntry + ExplorerPayload.selection
  lib/actions/click-outside.ts        — new (extracted from 3-component duplicate)
  lib/actions/long-press.ts           — new (mobile context menu / selection entry)
  lib/components/
    ActivityLog.svelte                — git-style timeline + parse() helper + response-shape parsing fix
    Activity/aggregate.ts             — new
    Activity/ActivityGraph.svelte     — new
    Explorer.svelte                   — multi-select, marquee, batch move, explicit Inspector trigger, background context menu
    Explorer/format.ts                — new (formatBytes / formatRelTime / formatFullTime)
    Explorer/Inspector.svelte         — new (w-[360px] side panel)
    Explorer/selection.ts             — new (toggleEntry / setSingle / selectRange / clear)
    Explorer/ContextMenu.svelte       — Details menu item + Info icon + onInspect prop
    Explorer/drag-drop.ts             — DragPayload {items, sourceLoc} + legacy wrap + performMoveBatch
    Explorer/Toolbar.svelte           — autocomplete=off + mobile condense + searchInput snippet
    Explorer/ToolbarOverflow.svelte   — new (mobile toolbar dropdown)
    Explorer/uploads.ts               — folder upload dotfile skip + summary toast
    Library/loader.ts                 — new (loadLibrary(kind))
    Library/MusicLibrary.svelte       — new
    Library/VideoLibrary.svelte       — new
    QuickOpen.svelte                  — mobile fullscreen + global file search + visualViewport
    Shell/Header.svelte               — hamburger + mobile condense
    Shell/MobileDrawer.svelte         — new
    Shell/nav-items.ts                — new (NAV_ITEMS shared)
    Shell/StatusBar.svelte            — safe-area-inset-bottom padding
    Shell/VerticalNav.svelte          — simplified via nav-items.ts import
    Tabs/TabBar.svelte                — mobile width shrink + close always visible + drag disabled
    Tabs/TabContent.svelte            — music-library / video-library routing
    Uploads/DragDropOverlay.svelte    — depth cap + dotfile + DEV harness
    Uploads/UploadPanel.svelte        — mobile bottom-sheet variant
    Viewers/VideoPlayer.svelte        — download fallback overlay on codec error
    Viewers/VideoControls.svelte      — mobile overflow + click-outside action
    Viewers/PdfViewer.svelte          — mobile overflow + click-outside action
    Viewers/MonacoViewer.svelte       — mobile wordWrap=on