Appearance
@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_aggregategains five sticky-override columns (manual_status,manual_started_at,manual_finished_at,manual_paused_at,manual_set_at). Whenmanual_statusis set it wins over the computed status everywhere —GET /api/library/{id}(now also returnsprogress),GET /api/library/sync,GET /api/reading-status/counts, andGET /api/reading-status/{status}— until the user clears it. Underlyingreading_progresshistory and the autostarted_at/finished_atderivation 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:unreadclears all dates,readingkeepsstarted_at,finishedkeepsstarted_at+finished_at,pausedkeepsstarted_at+paused_at. Rejects future dates andfinishedAt < startedAt.DELETE /api/library/{id}/reading-status— clears the override; subsequent reads fall back to the computed status from KoSync.
The
ProgressAggregateschema gainspausedAt(manual-only) andmanuallySet: booleanso 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_onto today andstarted_atfrom 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.tsand per-packagevite.config.tsfiles now hold allfmt/lint/pack/testconfig. Workspace orchestration runs throughvp run -r <task>(with-Ffilters replacing--filter). - Drop standalone
oxlint,oxfmt, andoxlint-tsgolintdev deps —vp lintandvp fmtship the same binaries. Per-package.oxlintrc.jsonfiles are removed (rules now live in the rootvite.config.tslintblock). services/api-honobuilds viavp pack(still tsdown under the hood) instead of invokingtsdowndirectly.tsdown.config.tscontent is inlined into thepackblock ofservices/api-hono/vite.config.ts.apps/obsidian-pluginkeepsesbuildfor the production build (Obsidian-specific output) but usesvpfortest/lint/fmt/check.apps/webbranches its plugin set onprocess.env.VITESTinsidevite.config.tsso component tests renderUBadgeetc. 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 separatevitest.config.ts— removed in the follow-up cleanup.)- All
vitest/vitest/configimports rewritten tovite-plus/test. Catalogviteandvitestentries are aliased to@voidzero-dev/vite-plus-coreand@voidzero-dev/vite-plus-testso transitive peer-dep resolutions still deduplicate correctly. - Lefthook hooks call
vp lint --fix/vp fmt/vp run check -rinstead ofpnpm exec. - CI: switched to the official
voidzero-dev/setup-vp@v1action (handles Node + pnpm + cache in one step, reading.node-version). Removed.turbocache steps, the manualcorepackblocks, and the now-defunctgenerate-typesstep.pnpm run Xinvocations rewritten tovp run -r X/vp run -F <pkg> X;pnpm execinvocations rewritten tovp exec.--affectedflags are dropped (no direct Vite+ equivalent yet);check/testnow run across all workspace packages every time. Docs-deploy change-detection regex updated fromturbo.jsontovite.config.ts. - Commit hooks: lefthook's pre-commit now calls
vp staged(Vite+'s native staged-file runner). The per-extension command map moved fromlefthook.ymlglob commands to astagedblock in the rootvite.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 tooxc-minify— eliminates the "plugin assigns to bundle variable" warnings and thetransformWithEsbuild is deprecatedwarning we saw on every docs build, and roughly halves docs build time (14s → 7s). Required addingoxc-minify(peer dep of vitepress 2 when running on rolldown-vite) to@libris/docsdevDeps. - Type-aware lint enabled globally — both
typeAwareandtypeCheckset totrueinlint.options(matching thevp create/vp migratedefault). Per-package--type-awareflags removed from scripts since it's now the default. Surfaced 32no-floating-promisesviolations inapps/web:onSettledcallbacks: rewrote each as() => queryCache.invalidateQueries(...)(single) or() => Promise.all([...])(multiple). Returning the promise is more idiomatic thanvoid-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 fromvoid bootstrap()to top-levelawait bootstrap()(supported by ourtarget: esnextbuild).apps/obsidian-plugin/src/main.ts:workspace.revealLeaf(leaf)properlyawaited inside the already-asyncactivateSyncStatusView.
MetadataFieldPicker.types.tsextracted fromMetadataFieldPicker.vue. tsgolint can lint the<script>block of a.vuefile but can't resolve types declared inside an SFC when they're imported from a.tsfile — types meant to cross the SFC boundary must live in a regular.tsmodule. The test now importsCandidatedirectly instead of relying on theInstanceType<typeof Component>["$props"]extraction trick.- Per-package
checkscripts collapsed to a singlevp checkinvocation. Previously each package ranvp fmt --check . && vp lint && tsc --noEmitas three serial subprocesses; withtypeCheck: trueenabled,vp checkis one cohesive run that does fmt + lint + tsgolint type-check.apps/webstill chains&& vue-tsc --buildbecause tsgolint can't see inside.vueSFCs (vue-tsc covers templates and Vue component types). Workspacechecknow executes 8 tasks instead of 13. - IDE integration (
vp createadds these by default;vp migratedoesn't):.vscode/extensions.jsonnow recommendsVoidZero.vite-plus-extension-pack, and.vscode/settings.jsonsetsnpm.scriptRunner: vpand the oxc formatter as the default + format-on-save + fix-on-save action. - CI gains a
node_modules/.vite/task-cacheactions/cachestep (per check / test / build / docs job), and the cacheable steps now usevp run --cache -r <task>. Verified locally: 11/13 cache hits and ~74s saved on a warm second run ofvp 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: trueadded to rootvite.config.ts. One-time reformat applied to everypackage.jsonin the workspace.lefthook.ymlnow carries an explanatory header noting why we don't runvp 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 buildall green locally.- Drop Turborepo. The root
e05296d: Finish the Vite+ migration: drop legacy script/task patterns and wire up the workspace task graph properly.
apps/web— collapsedvitest.config.tsinto thetestblock ofvite.config.ts(per Vite+ guidance: "We do not recommend usingvitest.config.tswith Vite+"). Plugin set is now branched onprocess.env.VITESTsoshallowMountkeeps seeing raw.u-badgeHTML instead of stubbed<u-badge-stub>components. Replaced thenpm-run-all2build (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-packagedependsOn: ['@libris/api-hono#generate:version']declared ontype-checkso standalonevp run -F @libris/web buildtriggers the upstream codegen — workspace dep ordering only kicks in for-r. Excludeddist/,node_modules/.nuxt-ui/, and the auto-generated.d.tsfiles from the input fingerprint to stop self-poisoning the cache. Droppednpm-run-all2from the catalog.services/api-hono— replaced thepre*+pnpm runhook chains withvite.config.tstasks anddependsOn. Five scripts (predev,prebuild,prebuild:spec,precheck,pretest) all forwarded topnpm run generate:version, andbuilddidpnpm run build:spec && vp pack && cp -r migrations dist/migrations.pnpm runinside 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:bullmqare all tasks with properdependsOnandcachesettings. InlineGOMEMLIMIT=1GiB GOGC=50kept inside thechecktask's command (cleaner thanuntrackedEnvfor a fixed knob —untrackedEnvonly 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 purevp run -Fwrappers. Promoted the three CI-style aggregators (check,test,build) and the two shell wrappers (bruno:import,test:e2e:docker) into root tasks invite.config.ts. Per-package work is now invoked directly viavp run -F @libris/<pkg> <task>. UpdatedREADME.md,docs/contributing.md,docs/database.md,docs/testing.md,docs/ci-cd.md,.github/pull_request_template.md, andAGENTS.md(CLAUDE.md is a symlink) to reflect the new invocation surface.Dockerfile — migrated to install
vite-plusglobally and usevp run -F @libris/<pkg> buildfor both stages. The previouspnpm run build/pnpm run generate:versioninvocations stopped working when those moved from package.json scripts to vite.config.ts tasks. Build-web stage now copiesservices/api-hono/vite.config.ts+scripts/so the cross-packagedependsOnresolves..forgejo/workflows/publish-images.yml— version job migrated from rawcorepack/pnpm install --frozen-lockfile/pnpm changeset versiontosetup-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 buildall green. Recursivevp run checkwarm 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
USlideovertoUPopoveranchored to the Filters button.Works around an interaction between reka-ui 2.9.3 (pulled in by
@nuxt/ui4.6.1) and Nuxt UI'sUSlideover: reka-ui'sComboboxInputgained anonBlurhandler that callsonOpenChange(false)when focus leaves bothrootContext.parentElementand the content element.SelectMenuinstantiatesComboboxRootwithas-child, soparentElementnever gets bound to a DOM node. InsideUSlideover'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 deterministiclibrary.spec.ts:331"filter panel filters the library by language and uploader" failure on the main E2E suite.UPopoveruses reka-ui'sPopoverprimitive (notDialog) and doesn't trap focus, so nestedUSelectMenubehaves 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/webis now the Vite app — same port (3100), same URL shape, same Hono-served static output in production (migrated from.output/publictodist). Deletesapps/web-viteas a separate package; the Nuxt sources underapps/webare 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 viavue-router/experimental/pinia-coladadrive detail-page queries. - Head/SEO:
@unhead/vue(wasuseSeoMetafrom Nuxt). - State: Pinia directly (was
@pinia/nuxt+useState); a Piniaauthstore replaces the formeruseState-keyed auth singletons. - Queries:
@pinia/coladadirectly (was@pinia/colada-nuxt). - VueUse:
@vueuse/coredirectly (was@vueuse/nuxt). - UI:
@nuxt/ui/vite+@nuxt/ui/vue-plugin(kept — the library is framework-agnostic). - ECharts:
vue-echartswith the<VChart>component registered globally (wasnuxt-echarts). - Error boundary: a local
ErrorBoundary.vueusingonErrorCaptured(was<NuxtErrorBoundary>). - Runtime config: split into
VITE_*env for build-time +/config.jsonfetch on boot; accessed via auseLibrisConfig()composable (wasuseRuntimeConfig).
Dropped catalog entries:
nuxt,nuxt-echarts,@pinia/nuxt,@pinia/colada-nuxt,@vueuse/nuxt. Dockerfile now copiesapps/web/distinto the runner image'spublic/(wasapps/web/.output/public); CI e2e packaging updated to match.Feature parity verified against the full Playwright suite: 130/130 passing (9
@externalHardcover-live tests skipped, same as before).- Build: Vite 8 +
Patch Changes
04bb6a8: Disable Nuxt telemetry to unblock
pnpm devThe first-run telemetry consent prompt reads stdin, but
turbo run devdoes 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-readyWebSocket handler calledrefresh(), which Pinia Colada treats as "refetch only when stale" — the cached page was still fresh (withinstaleTime) so no fetch happened and the UI looked stuck until a manual reload. Switched torefetch()which always refetches. - Cover was missing on the detail page until Hardcover matched. The detail view only probed
/api/inbox/:id/coverwhencoverUrlwas 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 ofcoverUrl.
- Rescan didn't auto-update the view. The
b8f7d26: Adopt the canonical
create-vue+ TypeScript script pattern forapps/web:checkand a new dedicatedtype-checkscript now callvue-tsc --build(wasvue-tsc --noEmit). Without--build,vue-tscloads the solutiontsconfig.jsonwhosefiles: []+referenceslayout means it was checking zero files — so type errors in.vuesources (like theimport.meta.client/colorMode.preferenceregressions caught only atvite buildtime in CI run 528) slipped straight throughcheck.--buildwalks the project references (tsconfig.app.json,tsconfig.node.json) the same way the build step does.buildrunstype-checkandbuild-onlyin parallel viarun-p(newnpm-run-all2catalog dep) instead of the old sequentialvue-tsc -b --noEmit && vite build. Either failing now aborts the build; neither blocks the other.- Both referenced tsconfigs set
tsBuildInfoFileto./node_modules/.tmp/tsconfig.*.tsbuildinfoso the incremental cache lives undernode_modules/instead of the package root, which lets us drop the*.tsbuildinfogitignore rule.
6c3d4db: Annotate the
@update:model-valuehandler on the series-indexUInputinMetadataFieldPicker.vue. Without the explicit(v: string | number) =>signature,vue-tsc --buildin the publish-images Docker stage hit TS7006 (implicitany) — the template-parent inference that the local incremental build relied on doesn't kick in on the container's cold--buildrun.b8f7d26: Fix two leftover Nuxt-isms flagged by the Vite build:
ColorModeToggle.vuewas reading/writingcolorMode.preference, but VueUse'suseColorMode()returns a writable ref of the value directly (not Nuxt's{ preference, value }object). The toggle would have crashed at runtime; we write/readcolorModedirectly now (and useautoinstead of Nuxt'ssystemname for the system-follow mode).SettingsHardcover.vueandsettings.vuegated their WebSocket listener registration behindif (import.meta.client). That flag is a Nuxt shim — in a pure Vite SPA it'sundefined, so the guards always skipped and the listeners never registered. Removed the guards sojob:failed,book:*, andhardcover: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