Skip to content

@libris/web

3.1.1

Patch Changes

  • Updated dependencies [bc775c9]
  • Updated dependencies [2376f08]
    • @libris/api-hono@0.29.0

3.1.0

Minor Changes

  • 19f7b77: Manual reading status override on the book detail page.

    API: reading_aggregate gains five sticky-override columns (manual_status, manual_started_at, manual_finished_at, manual_paused_at, manual_set_at). When manual_status is set it wins over the computed status everywhere — GET /api/library/{id} (now also returns progress), GET /api/library/sync, GET /api/reading-status/counts, and GET /api/reading-status/{status} — until the user clears it. Underlying reading_progress history and the auto started_at / finished_at derivation from KoSync are untouched, so the override is reversible.

    Two new endpoints:

    • PATCH /api/library/{id}/reading-status — body { status, startedAt?, finishedAt?, pausedAt? }. Per-status field policy: unread clears all dates, reading keeps started_at, finished keeps started_at + finished_at, paused keeps started_at + paused_at. Rejects future dates and finishedAt < startedAt.
    • DELETE /api/library/{id}/reading-status — clears the override; subsequent reads fall back to the computed status from KoSync.

    The ProgressAggregate schema gains pausedAt (manual-only) and manuallySet: boolean so clients can render a Clear-override affordance.

    Web: New "Edit reading status" action on the book detail page action menu opens a modal with a status select and date pickers that appear conditionally per status. Pre-fills finished_on to today and started_at from the existing aggregate. A "Clear override" button appears when an override is active. The Obsidian plugin remains read-only and consumes the effective (override-aware) status automatically via /api/library/sync.

Patch Changes

  • e05296d: Migrate the monorepo to Vite+ as the unified toolchain.

    • Drop Turborepo. The root vite.config.ts and per-package vite.config.ts files now hold all fmt / lint / pack / test config. Workspace orchestration runs through vp run -r <task> (with -F filters replacing --filter).
    • Drop standalone oxlint, oxfmt, and oxlint-tsgolint dev deps — vp lint and vp fmt ship the same binaries. Per-package .oxlintrc.json files are removed (rules now live in the root vite.config.ts lint block).
    • services/api-hono builds via vp pack (still tsdown under the hood) instead of invoking tsdown directly. tsdown.config.ts content is inlined into the pack block of services/api-hono/vite.config.ts.
    • apps/obsidian-plugin keeps esbuild for the production build (Obsidian-specific output) but uses vp for test/lint/fmt/check.
    • apps/web branches its plugin set on process.env.VITEST inside vite.config.ts so component tests render UBadge etc. as plain elements (matches the pre-migration test environment); the main path carries the full plugin set for dev/build. (Test-only config previously lived in a separate vitest.config.ts — removed in the follow-up cleanup.)
    • All vitest / vitest/config imports rewritten to vite-plus/test. Catalog vite and vitest entries are aliased to @voidzero-dev/vite-plus-core and @voidzero-dev/vite-plus-test so transitive peer-dep resolutions still deduplicate correctly.
    • Lefthook hooks call vp lint --fix / vp fmt / vp run check -r instead of pnpm exec.
    • CI: switched to the official voidzero-dev/setup-vp@v1 action (handles Node + pnpm + cache in one step, reading .node-version). Removed .turbo cache steps, the manual corepack blocks, and the now-defunct generate-types step. pnpm run X invocations rewritten to vp run -r X / vp run -F <pkg> X; pnpm exec invocations rewritten to vp exec. --affected flags are dropped (no direct Vite+ equivalent yet); check / test now run across all workspace packages every time. Docs-deploy change-detection regex updated from turbo.json to vite.config.ts.
    • Commit hooks: lefthook's pre-commit now calls vp staged (Vite+'s native staged-file runner). The per-extension command map moved from lefthook.yml glob commands to a staged block in the root vite.config.ts. Lefthook still owns the other hooks (prepare-commit-msg, pre-push, post-merge, post-checkout) for the beads integration.
    • AGENTS.md / CLAUDE.md: added the canonical <!--VITE PLUS START--> ... <!--VITE PLUS END--> block at the top so generic agents pick up the toolchain context without reading the project-specific section below.
    • VitePress bumped to 2.0.0-alpha.17. Adds native rolldown-vite support and switches the bundler to oxc-minify — eliminates the "plugin assigns to bundle variable" warnings and the transformWithEsbuild is deprecated warning we saw on every docs build, and roughly halves docs build time (14s → 7s). Required adding oxc-minify (peer dep of vitepress 2 when running on rolldown-vite) to @libris/docs devDeps.
    • Type-aware lint enabled globally — both typeAware and typeCheck set to true in lint.options (matching the vp create/vp migrate default). Per-package --type-aware flags removed from scripts since it's now the default. Surfaced 32 no-floating-promises violations in apps/web:
      • onSettled callbacks: rewrote each as () => queryCache.invalidateQueries(...) (single) or () => Promise.all([...]) (multiple). Returning the promise is more idiomatic than void-ing it — pinia-colada (and any other consumer) can await invalidation if it cares.
      • apps/web/src/main.ts: bootstrap() is the app entry point. Switched from void bootstrap() to top-level await bootstrap() (supported by our target: esnext build).
      • apps/obsidian-plugin/src/main.ts: workspace.revealLeaf(leaf) properly awaited inside the already-async activateSyncStatusView.
    • MetadataFieldPicker.types.ts extracted from MetadataFieldPicker.vue. tsgolint can lint the <script> block of a .vue file but can't resolve types declared inside an SFC when they're imported from a .ts file — types meant to cross the SFC boundary must live in a regular .ts module. The test now imports Candidate directly instead of relying on the InstanceType<typeof Component>["$props"] extraction trick.
    • Per-package check scripts collapsed to a single vp check invocation. Previously each package ran vp fmt --check . && vp lint && tsc --noEmit as three serial subprocesses; with typeCheck: true enabled, vp check is one cohesive run that does fmt + lint + tsgolint type-check. apps/web still chains && vue-tsc --build because tsgolint can't see inside .vue SFCs (vue-tsc covers templates and Vue component types). Workspace check now executes 8 tasks instead of 13.
    • IDE integration (vp create adds these by default; vp migrate doesn't): .vscode/extensions.json now recommends VoidZero.vite-plus-extension-pack, and .vscode/settings.json sets npm.scriptRunner: vp and the oxc formatter as the default + format-on-save + fix-on-save action.
    • CI gains a node_modules/.vite/task-cache actions/cache step (per check / test / build / docs job), and the cacheable steps now use vp run --cache -r <task>. Verified locally: 11/13 cache hits and ~74s saved on a warm second run of vp run --cache -r check. Tasks that mutate their own inputs (e.g. @libris/docs#check, @libris/api-hono#build:spec) are correctly identified as non-cacheable.
    • fmt.sortPackageJson: true added to root vite.config.ts. One-time reformat applied to every package.json in the workspace.
    • lefthook.yml now carries an explanatory header noting why we don't run vp config (we already use lefthook for the broader hook lanes — pre-push, post-merge, post-checkout, prepare-commit-msg — which integrate beads).

    Validation: vp install, vp run -r check, vp run -r test, vp run -r build all green locally.

  • e05296d: Finish the Vite+ migration: drop legacy script/task patterns and wire up the workspace task graph properly.

    apps/web — collapsed vitest.config.ts into the test block of vite.config.ts (per Vite+ guidance: "We do not recommend using vitest.config.ts with Vite+"). Plugin set is now branched on process.env.VITEST so shallowMount keeps seeing raw .u-badge HTML instead of stubbed <u-badge-stub> components. Replaced the npm-run-all2 build (run-p type-check "build-only {@}") with a Vite Task graph: type-check (vue-tsc), build (vp build, dependsOn type-check), check (vp check, dependsOn type-check). Cross-package dependsOn: ['@libris/api-hono#generate:version'] declared on type-check so standalone vp run -F @libris/web build triggers the upstream codegen — workspace dep ordering only kicks in for -r. Excluded dist/, node_modules/.nuxt-ui/, and the auto-generated .d.ts files from the input fingerprint to stop self-poisoning the cache. Dropped npm-run-all2 from the catalog.

    services/api-hono — replaced the pre* + pnpm run hook chains with vite.config.ts tasks and dependsOn. Five scripts (predev, prebuild, prebuild:spec, precheck, pretest) all forwarded to pnpm run generate:version, and build did pnpm run build:spec && vp pack && cp -r migrations dist/migrations. pnpm run inside a script bypasses the Vite Task graph (separate process, no cache, no inlining). Now: generate:version, build:spec, build, check, test, dev, db:reset, db:studio, reset:bullmq are all tasks with proper dependsOn and cache settings. Inline GOMEMLIMIT=1GiB GOGC=50 kept inside the check task's command (cleaner than untrackedEnv for a fixed knob — untrackedEnv only passes through values the caller has set). Input filters added to exclude self-written outputs (src/generated/, openapi.json, dist/).

    docs — added input filters to the vitepress tasks: .vitepress/dist/, .vitepress/cache/, .vitepress/.temp/, node_modules/.vite-temp/. With these excluded, the docs build is now 100% cache-hit on warm runs (~9s saved per invocation).

    Root package.json + vite.config.ts — dropped ~15 thin alias scripts (dev:server, dev:web, dev:docs, build:docs, test:api, db:reset, db:studio, reset:bullmq, docs:generate:db, …) that were pure vp run -F wrappers. Promoted the three CI-style aggregators (check, test, build) and the two shell wrappers (bruno:import, test:e2e:docker) into root tasks in vite.config.ts. Per-package work is now invoked directly via vp run -F @libris/<pkg> <task>. Updated README.md, docs/contributing.md, docs/database.md, docs/testing.md, docs/ci-cd.md, .github/pull_request_template.md, and AGENTS.md (CLAUDE.md is a symlink) to reflect the new invocation surface.

    Dockerfile — migrated to install vite-plus globally and use vp run -F @libris/<pkg> build for both stages. The previous pnpm run build / pnpm run generate:version invocations stopped working when those moved from package.json scripts to vite.config.ts tasks. Build-web stage now copies services/api-hono/vite.config.ts + scripts/ so the cross-package dependsOn resolves.

    .forgejo/workflows/publish-images.yml — version job migrated from raw corepack/pnpm install --frozen-lockfile/pnpm changeset version to setup-vp@v1 + vp install --frozen-lockfile + vp exec changeset version, matching the rest of CI.

    Validation: vp run check, vp run -F @libris/web test, vp run -F @libris/api-hono test, vp run -F @libris/obsidian-plugin test, vp run -F @libris/api-hono build, vp run -F @libris/web build, vp run -F @libris/docs build all green. Recursive vp run check warm cache: 5/7 hits, 61s saved. Docker image build couldn't be validated locally (Docker not present in WSL) — flag for human verification on next image publish.

  • Updated dependencies [19f7b77]

  • Updated dependencies [e05296d]

  • Updated dependencies [e05296d]

    • @libris/api-hono@0.28.0

3.0.2

Patch Changes

  • Updated dependencies [c58d4f3]
  • Updated dependencies [c58d4f3]
    • @libris/api-hono@0.27.0

3.0.1

Patch Changes

  • b749ae5: Move library filters from USlideover to UPopover anchored to the Filters button.

    Works around an interaction between reka-ui 2.9.3 (pulled in by @nuxt/ui 4.6.1) and Nuxt UI's USlideover: reka-ui's ComboboxInput gained an onBlur handler that calls onOpenChange(false) when focus leaves both rootContext.parentElement and the content element. SelectMenu instantiates ComboboxRoot with as-child, so parentElement never gets bound to a DOM node. Inside USlideover's focus trap, focus briefly transitions to the trigger when the combobox opens — the blur handler then falsely concludes focus went outside and closes the popover ~40 ms after it opened. Playwright observed this as the deterministic library.spec.ts:331 "filter panel filters the library by language and uploader" failure on the main E2E suite.

    UPopover uses reka-ui's Popover primitive (not Dialog) and doesn't trap focus, so nested USelectMenu behaves normally. No upstream patch or pnpm override needed.

    UX change: the filter panel now anchors under the Filters button instead of sliding in from the right. The filter widgets, clear/done buttons, and active-filter badge are unchanged.

  • Updated dependencies [9193223]

  • Updated dependencies [7119824]

    • @libris/api-hono@0.26.0

3.0.0

Major Changes

  • b8f7d26: BREAKING: replace the Nuxt 4 frontend with a Vue 3 + Vite 8 SPA in place. apps/web is now the Vite app — same port (3100), same URL shape, same Hono-served static output in production (migrated from .output/public to dist). Deletes apps/web-vite as a separate package; the Nuxt sources under apps/web are gone.

    Stack changes:

    • Build: Vite 8 + @vitejs/plugin-vue (was Nuxt 4 + Nitro).
    • Routing: vue-router 5 with its built-in file-based routing (vue-router/unplugin/vite); data loaders via vue-router/experimental/pinia-colada drive detail-page queries.
    • Head/SEO: @unhead/vue (was useSeoMeta from Nuxt).
    • State: Pinia directly (was @pinia/nuxt + useState); a Pinia auth store replaces the former useState-keyed auth singletons.
    • Queries: @pinia/colada directly (was @pinia/colada-nuxt).
    • VueUse: @vueuse/core directly (was @vueuse/nuxt).
    • UI: @nuxt/ui/vite + @nuxt/ui/vue-plugin (kept — the library is framework-agnostic).
    • ECharts: vue-echarts with the <VChart> component registered globally (was nuxt-echarts).
    • Error boundary: a local ErrorBoundary.vue using onErrorCaptured (was <NuxtErrorBoundary>).
    • Runtime config: split into VITE_* env for build-time + /config.json fetch on boot; accessed via a useLibrisConfig() composable (was useRuntimeConfig).

    Dropped catalog entries: nuxt, nuxt-echarts, @pinia/nuxt, @pinia/colada-nuxt, @vueuse/nuxt. Dockerfile now copies apps/web/dist into the runner image's public/ (was apps/web/.output/public); CI e2e packaging updated to match.

    Feature parity verified against the full Playwright suite: 130/130 passing (9 @external Hardcover-live tests skipped, same as before).

Patch Changes

  • 04bb6a8: Disable Nuxt telemetry to unblock pnpm dev

    The first-run telemetry consent prompt reads stdin, but turbo run dev does not allocate a TTY to child tasks. On a fresh clone this made the web dev server accept TCP on port 3100 but never respond, because Nuxt was blocked waiting for input that could not arrive.

  • b8f7d26: Fix two pre-existing bugs on the inbox detail page:

    • Rescan didn't auto-update the view. The book:metadata-ready WebSocket handler called refresh(), which Pinia Colada treats as "refetch only when stale" — the cached page was still fresh (within staleTime) so no fetch happened and the UI looked stuck until a manual reload. Switched to refetch() which always refetches.
    • Cover was missing on the detail page until Hardcover matched. The detail view only probed /api/inbox/:id/cover when coverUrl was already set on the book row, even though the backend extracts the embedded cover from any EPUB file. The inbox list view already worked that way — detail now matches: any EPUB → attempt extraction, regardless of coverUrl.
  • b8f7d26: Adopt the canonical create-vue + TypeScript script pattern for apps/web:

    • check and a new dedicated type-check script now call vue-tsc --build (was vue-tsc --noEmit). Without --build, vue-tsc loads the solution tsconfig.json whose files: [] + references layout means it was checking zero files — so type errors in .vue sources (like the import.meta.client / colorMode.preference regressions caught only at vite build time in CI run 528) slipped straight through check. --build walks the project references (tsconfig.app.json, tsconfig.node.json) the same way the build step does.
    • build runs type-check and build-only in parallel via run-p (new npm-run-all2 catalog dep) instead of the old sequential vue-tsc -b --noEmit && vite build. Either failing now aborts the build; neither blocks the other.
    • Both referenced tsconfigs set tsBuildInfoFile to ./node_modules/.tmp/tsconfig.*.tsbuildinfo so the incremental cache lives under node_modules/ instead of the package root, which lets us drop the *.tsbuildinfo gitignore rule.
  • 6c3d4db: Annotate the @update:model-value handler on the series-index UInput in MetadataFieldPicker.vue. Without the explicit (v: string | number) => signature, vue-tsc --build in the publish-images Docker stage hit TS7006 (implicit any) — the template-parent inference that the local incremental build relied on doesn't kick in on the container's cold --build run.

  • b8f7d26: Fix two leftover Nuxt-isms flagged by the Vite build:

    • ColorModeToggle.vue was reading/writing colorMode.preference, but VueUse's useColorMode() returns a writable ref of the value directly (not Nuxt's { preference, value } object). The toggle would have crashed at runtime; we write/read colorMode directly now (and use auto instead of Nuxt's system name for the system-follow mode).
    • SettingsHardcover.vue and settings.vue gated their WebSocket listener registration behind if (import.meta.client). That flag is a Nuxt shim — in a pure Vite SPA it's undefined, so the guards always skipped and the listeners never registered. Removed the guards so job:failed, book:*, and hardcover:sync-* events actually update the settings UI again.
  • Updated dependencies [b8f7d26]

  • Updated dependencies [4c007d2]

  • Updated dependencies [9da0f5d]

  • Updated dependencies [6aa46fe]

  • Updated dependencies [b8f7d26]

  • Updated dependencies [b8f7d26]

    • @libris/api-hono@0.25.0