57 Commits
Author SHA1 Message Date
SaladDay 2589384e68 feat(core): add first-class Pi session indexing (#23)
Pi cannot be read as another linear JSONL stream. Its history is a tree with
a durable leaf, orphan roots, branch summaries, and two compaction forms, so
the active context is something the format states rather than something line
order implies. The adapter keeps those semantics inside itself and projects
the result into the existing canonical tables.

Sessions are keyed by (normalized header cwd, header id) rather than by path,
because Pi's --session-id lookup is project-local: two projects may reuse an
id, while a move or an identical copy is still one session. Discovery covers
both layouts Pi writes and fingerprints each file by mtime, ctime, size and
inode, so a rewrite that preserves mtime is not read as unchanged.

Abandoned branches are preserved rather than dropped. Visibility becomes
three-state -- visible, inactive, hidden -- and helpers return only visible
rows until includeInactive asks for the superseded path, labeling every row
so a caller knows which it holds. Usage counts all three, because an
abandoned call still spent tokens; message_count reports only the visible
transcript.

A committed MIT-licensed oracle transcribed from Pi 0.83.0 pins the context
algorithms, and a fixed-seed differential runs 512 generated sessions against
it on every test run. Schema changes are additive.
2026-08-04 23:33:01 +08:00
tommy0103 47df0fe76a fix(app): use the project menu pattern for editor picker (#30)
Replace the input-like native select with the same compact button and floating menu vocabulary used by existing source filters.
2026-08-04 04:57:01 +08:00
tommy0103 23adfa3847 fix(app): align Settings controls and version metadata
The editor select referenced an undefined style class, and the renderer hard-coded 0.1.0 instead of reading Electron app metadata.
2026-08-03 21:34:14 +08:00
tommy0103 cb191cfad4 chore(release): prepare Obelisk v0.2.2 2026-08-03 21:34:14 +08:00
tommy0103andClaude Opus 5 38a8529d0c fix(renderer): anchor the reader from image mount, not image load
A decoder sizes an image from its header, so the row grows as soon as the
first bytes are parsed -- measured here at 421ms against a load event at
2407ms. Marking the row only once load fired left that first and largest
growth outside the compensation window, and scrolling back through history
still moved the reader by the full image height.

Rows are now marked from the moment an image mounts until loading finishes,
counted so a message with several images stays marked until the last one is
done. The listener also moves to the document: rows announce their images
while mounting, before a ref-driven listener on the timeline element would
exist to hear the first one, which is why the mount signal was being lost.

The fixture serves a real PNG and holds the response open, so the row grows
long before load the way a large image does; an SVG served in one shot lays
out and fires load together and cannot reproduce this. A companion assertion
checks the fixture still has that gap, so it cannot quietly stop testing the
timing it exists to test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 03:47:16 +08:00
tommy0103andClaude Opus 5 b72e0fcaf3 test(renderer): run every Electron suite from one command
The Electron suites were only reachable one npm script at a time, so a new
one was covered by whoever remembered it existed. test:electron:all builds
once and runs all five, reporting a single summary and a non-zero exit.

The runner lives under tests/ rather than app/scripts/, which .gitignore
excludes entirely.

The image row spacing assertion also stops hard-coding the current gap --
it had 0.59px of headroom -- and calibrates against the spacing the rest of
the timeline is using.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 02:12:38 +08:00
tommy0103andClaude Opus 5 5f7f6d7cd5 refactor(renderer): configure the Markdown image renderer at startup
renderMarkdown ran the marked configuration on every call and leaned on a
module-level flag to make that cheap. It belongs next to the custom element
registration, which already runs once, before anything renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 02:10:50 +08:00
tommy0103andClaude Opus 5 1124758b6b refactor(renderer): give session images one unavailable state, off the DOM
A source the Markdown renderer refuses now goes through the same element
with no src rather than a separate light-DOM span, so a blocked source and
a source that fails to load are one piece of UI instead of two. That drops
the span's stylesheet rule and the only reason the renderer built elements
just to serialise them.

Decoding what marked escaped no longer runs untrusted markup through a
detached element's innerHTML; the entities marked actually emits are
decoded in one pass, and attributes are escaped on the way out. With the
DOM dependency gone the renderer is directly unit-testable, so the escaping
and the protocol allowlist now have coverage that does not need Electron.

The element's shadow styles also stop hard-coding values that already exist
as tokens -- custom properties cross the shadow boundary, so --muted and
--hairline-strong are now the single source of truth -- and the height cap
becomes --session-image-max-block, which compact Markdown surfaces (subagent
panes, memory rows, tool results) lower from a viewport fraction to 240px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 02:10:50 +08:00
tommy0103andClaude Opus 5 a9e6d1bab8 test(renderer): cover image reader anchoring, and fail instead of hang
The wide-image probe resolved only from a load handler that a regression
could keep from ever firing, and it was awaited bare, so a broken build
hung the run instead of reporting a failure. Every renderer probe now runs
against a deadline, the probe rejects on image error, and the suite as a
whole is bounded.

Adds a held image endpoint so an above-viewport image can be made to finish
at a moment the test controls, and asserts the reader does not move -- both
at rest and mid-gesture, which is where it regressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 01:29:02 +08:00
tommy0103andClaude Opus 5 dde076153a fix(renderer): keep the reader anchored when a session image settles
virtual-core skips scroll compensation when an already-measured row above
the viewport is re-measured during an upward scroll: that is normally an
estimate correction, and compensating it makes rows jump while the reader
scrolls back through history. An image finishing loading is not an estimate
correction -- the row really did get taller -- so skipping compensation
pushed everything on screen down by the full image height. Scrolling back
through a session with screenshots moved the reader by ~490px per image.

The element now announces load and error through a composed event, and the
timeline compensates size changes for rows that just settled media while
leaving virtual-core's guard in place for every other re-measurement.

loading="lazy" comes off the image at the same time. Rows are only mounted
within a few viewports, so the attribute bought almost nothing while making
load timing depend on Chromium's connection heuristics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 01:29:02 +08:00
tommy0103andClaude Opus 5 34bd3aed83 fix(renderer): accept both marked image renderer signatures
marked <= 14 calls renderer.image(href, title, text); marked >= 15 passes
the token instead. With only the positional form handled, an upgrade of the
pinned CDN build would turn every session image into fallback text without
any error, so normalise both shapes and cover them with a unit test.

The tag name moves into session-image-contract.js so the Markdown renderer
no longer reaches it through the module that imports the .vue component,
which is what kept it out of Node's test runner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 01:28:45 +08:00
KinomotoMio e4a03fa58f chore(renderer): merge main into image rendering fix 2026-07-31 15:23:13 +08:00
tommy0103andClaude Opus 5 c3751f0f6b feat(app): open local file references at the referenced line
Mark absolute Markdown links and inline-code relative paths as file
references, resolve them against the originating message cwd, and open
them in the configured editor. Local links no longer navigate the SPA
away from the running renderer.

The canonical transcript assembly projected messages down to seven
fields, so cwd and session_id never reached the renderer. Both are added
to SessionDetailMessage: cwd because the working directory can change
mid-session, session_id because it scopes which roots a reference may
resolve inside. The main process derives those roots from the database
rather than trusting anything the renderer sends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 05:03:21 +08:00
tommy0103andClaude Opus 5 9890ae78b1 chore(app): upgrade Electron 33 -> 43
Electron 33 bundles Node 20.18, which has no type stripping. Every
tests/electron-*.mjs harness imports session-detail-assembly.mjs, which
in turn imports packages/core/src/session-detail.ts, so all three failed
at load with ERR_UNKNOWN_FILE_EXTENSION. Electron 43 bundles Node 24.18
and runs them.

better-sqlite3 11 does not compile against the newer V8 (removed
Context::GetIsolate, PropertyCallbackInfo::This, changed
SetNativeDataProperty signature), so the native dependency moves to 13
to match the ABI. electron-builder moves to 26 for Electron 43 support.

Verified: npm test 275/275, typecheck, electron-vite build, electron-builder
pack, and all three harnesses (15 + 55 + 11 assertions).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 03:40:07 +08:00
KinomotoMio 3b93ac2082 test(renderer): cover session image layout 2026-07-30 00:30:34 +08:00
KinomotoMio c079c50376 fix(renderer): route markdown images through media element 2026-07-30 00:25:58 +08:00
KinomotoMio ff6386c6da feat(renderer): add bounded session image element 2026-07-30 00:24:10 +08:00
tommy0103 75654f30b6 chore: remove demo html 2026-07-22 01:28:54 +08:00
tommy0103 28fa99ac94 feat: improve session sharing and Kimi transcript support
- normalize Kimi thinking parts and omit empty placeholders
- add standalone session-sharing and UI demo artifacts
- add regression coverage and refresh the Kimi index marker
- bump the Electron app and CLI packages to v0.2.1
- ignore generated app build assets
2026-07-21 19:29:14 +08:00
tommy0103 f79f1b3e3b refactor: add canonical transcript assembly seam 2026-07-21 00:58:34 +08:00
tommy0103 3ee44de4e5 feat: add registry-driven Kimi provider 2026-07-20 22:43:23 +08:00
tommy0103 21c3a1b9fc chore(release): prepare Obelisk v0.2.0 2026-07-20 01:58:50 +08:00
tommy0103 8864a36955 chore(release): prepare Obelisk v0.2.0 2026-07-20 01:58:22 +08:00
tommy0103 d399ab154f fix(app): stabilize timeline scrolling during updates
Publish real virtual-row measurements while suppressing momentum-breaking scroll writes, then reconcile the reader anchor once scrolling settles. Keep live patches out of the timeline during active gestures and add Electron coverage for tall-row overlap, residual motion, and existing-message updates.
2026-07-17 02:05:22 +08:00
tommy0103 d9f31bbe8a fix(app): preserve session reader state across navigation
Cache semantic timeline anchors and disclosures per session instead of relying on KeepAlive. Restore state after virtualized layout stabilization while preserving explicit focus and tail-follow behavior.
2026-07-16 23:22:47 +08:00
tommy0103 90191e4604 feat(cli): extract Obelisk runtime into npm package
Add @obelisk-apps/cli with the existing build, search, query, and attune contract plus official skill installation.

Separate the docs-only skill artifact, bootstrap installer, release layout, cross-platform CI, and package-level regression coverage.
2026-07-16 17:26:23 +08:00
tommy0103 1e4e5ec2d8 fix(app): isolate live session updates from scrolling
Defer and coalesce global catalogue refreshes while SessionDetail is active, carry session metadata through incremental patches, and gate in-flight catalogue commits by route. Harden the virtual timeline cold-open and user-scroll lifecycle, and add Electron frame, continuity, metadata, and overlap regressions.
2026-07-15 22:12:40 +08:00
tommy0103 5294d76f07 fix(app): prevent scrollend position rollback
Discard virtualizer measurement corrections suppressed during an active user gesture instead of replaying stale offsets at scrollend. Preserve post-scroll live anchoring and add unit plus Electron coverage for ordinary scrolling without updates.
2026-07-15 02:19:55 +08:00
tommy0103 82d9fbf657 fix(app): preserve momentum during live session updates
Track real user scrolling through scrollend with a quiet watchdog, defer timeline commits and virtualizer corrections until settlement, and make SessionDetail the sole tail-follow owner. Add unit and Electron regressions for scroll writes, reader anchoring, near-tail escape, explicit navigation, and flap timing.
2026-07-15 01:35:05 +08:00
tommy0103 b58c34d3af perf(app): stabilize live session timeline updates
Virtualize SessionDetail rows behind stable presentation boundaries and apply typed incremental patches only after visible commits. Preserve reader state across live updates, handle coalesced and reordered patches, and verify the 120Hz append path with Electron tracing.
2026-07-14 18:11:51 +08:00
tommy0103 2cad3b554d feat(app): virtualize session timeline
Render SessionDetail through measured dynamic-height virtual rows while preserving disclosure state, UUID navigation, reader anchoring, and tail-follow across live updates.

Add focused state/reconciliation tests plus a production Electron harness covering long-session DOM bounds, scrolling performance, offscreen navigation, and live viewport stability.
2026-07-14 03:45:39 +08:00
tommy0103 bbf16d8f9f fix(app): keep live session updates responsive
Cache timeline DOM indexes and memoize the timeline so progress and flap updates avoid rescanning or rerendering old messages. Preserve scroll anchors and disclosure state across targeted live message replacements.
2026-07-14 00:53:38 +08:00
tommy0103 7e6e9d9fc3 fix(app): keep live session updates incremental
Preserve unchanged message identities and isolate Vue row rendering during live refreshes.

Use a tail-only scroll path so updates do not interrupt active readers or expanded tools. Add regression coverage for 908-message appends, changed snapshots, and scroll policy.
2026-07-13 22:31:12 +08:00
tommy0103 f963f14b10 fix: stabilize live session UI and skill publishing
Keep message navigation and progress state synchronized, and preserve
sequential split-flap count updates with a bounded queue.

Publish the Obelisk skill under skills/obelisk for npx skills, sharing
the same staging layout between CI and local releases with regression coverage.
2026-07-13 22:02:44 +08:00
tommy0103andCodex a9687ba8b7 feat(app): live session update + tool renderer + input_tokens migration
SessionDetail live update:
- Extract session-view-state.mjs: capture scroll position, disclosure
  (open/skill-md-open) state, and visible-UUID anchor before refresh;
  reconcile messages by UUID (in-place update, append tail only); restore
  scroll and disclosure state after DOM patch. findLastMessageAtOrAbove uses
  binary search (O(log n)) instead of linear scan.
- scrollRevision tracks user scrolls during refresh to avoid stale anchors
  overriding manual navigation.
- Throttle onScroll to one rAF per frame.

Tool renderer:
- Extract tool-renderer.js: standalone module for rendering tool call cards
  (Read/Write/Edit diffs, Bash terminal output, search results, JS/TS
  syntax highlighting). Replaces inline rendering in SessionDetail.
- tests/app-tool-renderer.test.mjs covers escaping, highlighting, and
  terminal formatting.

Input tokens semantics migration:
- Claude provider now sums input_tokens + cache_creation_input_tokens +
  cache_read_input_tokens into a single input_tokens value (was previously
  only the raw field, undercounting when cache tokens are present).
- One-time index-wide re-parse triggered when the marker
  __claude_input_tokens_include_cache_v1__ is absent and the DB already
  has token data (self-healing on first build after upgrade).
- App indexer.ts carries the same marker check for the app's build path.

Also:
- PRODUCT.md: product register (users, purpose, brand, design principles,
  accessibility targets).
- README.md: minor wording updates.

Co-Authored-By: Codex (GPT-5) <noreply@openai.com>
2026-07-13 21:02:38 +08:00
tommy0103 78aacfac6c feat(session): animate total count with split flap 2026-07-12 20:00:07 +08:00
tommy0103 1742d73b32 fix(activity): align ledger typography 2026-07-12 15:24:28 +08:00
tommy0103 48f1a4c225 feat(activity): add contribution ledger 2026-07-12 15:15:48 +08:00
tommy0103 644942202e Complete Vue renderer migration 2026-07-12 02:26:01 +08:00
tommy0103 dbf3ea4b51 Fix activity session navigation 2026-07-12 01:47:32 +08:00
tommy0103 32a9b8d424 Merge branch 'refactor/migrate' into main
Phases 0–7 of the Obelisk engineering refactor: provider-adapter architecture
(ADR-0001), shared persist + tx module, TypeScript workspace (packages/core),
electron-vite app migration, writer-lease concurrency, and CI skill publish.
2026-07-12 01:12:32 +08:00
tommy0103 535d8edb4d refactor(core): finish TypeScript workspace migration 2026-07-12 00:37:32 +08:00
tommy0103 83d5703f7b refactor(core): move shared runtime into workspace package 2026-07-12 00:28:17 +08:00
tommy0103andClaude Opus 4.8 e3e61cc7ab fix: coordinate sqlite index writers
Implement the full ADR-0006 plan: three-layer separation of transaction
correctness, retry policy, and cross-process writer coordination.

Layer 1 — scripts/tx.ts (transaction correctness):
- runWriteTransaction executes work exactly once; no internal retry.
- BEGIN IMMEDIATE takes the write lock up front (avoids SQLITE_BUSY_SNAPSHOT).
- Guarded rollback: checks inTransaction() via adapter before attempting
  ROLLBACK; never masks the primary exception.
- WriteTxDiagnostics attached to errors: phase, code, label,
  rollbackSucceeded, rollbackError, transactionActive.
- Binding adapters (betterSqliteTransactionAdapter, nodeSqliteTransactionAdapter)
  mapping better-sqlite3's `.inTransaction` and node:sqlite's `.isTransaction`.
- configureConnection centralizes WAL + synchronous + busy_timeout.

Layer 2 — scripts/write-coordinator.ts (retry policy):
- runRetryableWriteTransaction: bounded retry with total time budget.
- Only retries when the transaction confirmed ended (transactionActive=false)
  and the error is SQLITE_BUSY during work/commit phase.
- BEGIN-phase BUSY = abort entire build (isBeginBusyFailure); the caller
  returns `{ deferred: true, reason: 'writer_busy' }` instead of waiting.
- hasUnusableTransaction detects a still-active transaction after failure;
  aborts the build immediately, never retries.

Layer 3 — scripts/writer-lease.ts (cross-process coordination):
- acquireWriterLease: dedicated writer.lock.sqlite with busy_timeout=0 +
  BEGIN IMMEDIATE. Non-blocking attempt; bounded wait with retryDelayMs.
- writerLockPathFor derives lock path from the target DB path.
- Lease held for the entire build; released on completion or failure.
- Lock DB uses DELETE journal (not WAL); crash/close auto-releases.
- All consumers obey: skill acquires at build start (returns deferred if
  unavailable); app daemon (via worker) acquires for its build cycle.

Build semantics changes:
- affectedSessionIds updated only after successful commit.
- BuildIndexResult gains skipped/skippedFiles for observability.
- Skill finalize failure now fails the build (was silently warned).
- Checkpoint changed to PASSIVE (TRUNCATE reserved for maintenance/exit).
- Skill buildIndex returns { deferred, reason } on lease contention;
  indexer-service reschedules the build (deferredRetryMs) without publishing
  a heartbeat (so the build-deferred state is visible to cross-process
  arbitration).
- Service publishes heartbeat immediately on start() for correct arbitration.

Tests:
- tests/write-transaction.test.mjs: single-shot execution, diagnostics
  propagation, auto-rolled-back transaction detected, rollback failure
  captured as metadata, BEGIN IMMEDIATE semantics.
- tests/writer-lease.test.mjs: acquire/release, contention returns null,
  bounded wait with release during budget.
- tests/app-writer-lease.test.mjs: better-sqlite3 adapter integration.
- tests/app-rollback-guard.test.mjs: rewritten — transient BUSY recovered
  by coordinator, persistent BUSY skips file, begin-busy aborts build,
  live-transaction aborts build, phantom affectedSessionIds prevented.
- tests/daemon-arbitration.test.mjs: skill defers to fresh app heartbeat,
  builds when heartbeat is stale.
- tests/app-indexer-service.test.mjs: new cases for deferred-retry
  scheduling and immediate heartbeat on start.
- app/tests/electron-concurrency.mjs + child: dual-child IPC structure for
  real better-sqlite3 contention (holder acquires lock → build child starts
  → delayed release → result collected; persistent contention bounded).

ADR-0006 updated to reflect the implemented design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:42:43 +08:00
tommy0103andClaude Opus 4.8 44029676d4 fix(indexer): guard cleanup rollback so it never masks the real build error
Reported as "cannot rollback - no transaction is active" — a secondary error.
SQLite auto-rolls back certain failures (SQLITE_BUSY / SQLITE_BUSY_SNAPSHOT,
disk full), after which the per-file loop's unguarded ROLLBACK in its catch
threw over the real error and aborted the whole build instead of skipping just
the bad file.

Stopgap only (see docs/adr/0006 for the planned full fix):
- Add safeRollback(db) in both indexers: it swallows only the rollback's own
  error, so the true cause surfaces. Per-file failures are logged and the build
  continues; the finalize failure still propagates.
- Give the skill's node:sqlite connection an explicit PRAGMA busy_timeout=5000
  (it has no default). The app adds none: better-sqlite3 already defaults to
  5000ms, so busy_timeout is NOT the root-cause fix and is not treated as one.

Add tests/app-rollback-guard.test.mjs: injects a DB that faithfully reproduces
"a write auto-rolls back the txn, then ROLLBACK errors" and asserts the build
survives (bad file skipped, other file indexed). Revert-checked: without the
guard the test fails with the exact "cannot rollback - no transaction is active".

docs/adr/0006 records the real fix (shared runWriteTransaction, single-writer
coordination, BEGIN IMMEDIATE, whole-transaction retry, PASSIVE checkpointing)
as deferred, two-phase work — and why bumping busy_timeout is not it.

Verified: suite 124/124, typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:36:36 +08:00
tommy0103andClaude Opus 4.8 01a390fa10 refactor(app): migrate main + preload to TypeScript with typed seams (Phase 5d-3c-ii)
Convert the app's main and preload source from .js to .ts (git mv preserves
history), adding types where they carry value: the core-consumption seam
(BuildIndexOptions/BuildIndexResult, FileInfo), the indexer service/worker
factories, and the preload IPC bridge. Module-to-module specifiers use the
real .ts extension (mirroring scripts/, since Node type-stripping does not
rewrite .js->.ts); the worker's runtime path stays indexer-worker.js because
that is the built output.

Toolchain:
- Add app/tsconfig.json: strict but noImplicitAny:false (the app orchestrates
  the already-strict core; annotating every SQLite-handle helper is low-value
  churn) + allowImportingTsExtensions (safe under noEmit).
- Add @types/better-sqlite3 for the injected binding.
- electron.vite.config.ts inputs -> .ts; refresh the stale CommonJS comment.
- typecheck script runs root + app projects. Root tsconfig excludes the
  app-importing tests (app-*.test.mjs, recap-capture-query.test.mjs) so the
  lenient app files are not dragged into the strict root program; the app
  source is covered by app/tsconfig.json instead. See docs/adr/0005.

Verified: npm run typecheck (root + app) clean; suite 121/121; electron-vite
build emits all 6 main entries + preload with no .ts/node:sqlite residue in
the bundles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 16:25:59 +08:00
tommy0103andClaude Opus 4.8 60d47a852e refactor(app): consume shared indexing core, remove duplicated indexer (Phase 5d-3c-i)
The desktop app now indexes through the shared provider adapters + persist
layer (scripts/providers/{claude,codex}, scripts/persist, scripts/parsing)
instead of maintaining its own parallel indexer. buildIndex shrinks from
~1173 to ~592 lines, eliminating the skill<->app parse duplication that
Phase 5 set out to remove. electron-vite bundles the .ts core from source
with better-sqlite3 injected; the provider->parsing graph stays
node:sqlite-free so nothing drags node:sqlite into the app.

Also fix a misleading log: when a manual rebuild tears down the worker
mid-build, the cancelled background build is a deliberate stop, not a
failure. Guard the service's failure log with the stopped flag so it no
longer prints "Obelisk index build failed: Indexer worker stopped" on
every rebuild.

- CONTEXT.md: provider-adapter + single-persist + node:sqlite-free parsing.
- docs/adr/0005: app builds with electron-vite (TS+ESM), packages with
  electron-builder; preload CJS for sandbox; app consumes core from source.

Verified: full suite 121/121; a node:sqlite-adapter dogfood of the rebuild
path over real data (969 files, 285 sessions, FTS rebuilt) runs clean; app
Rebuild confirmed in real Electron/better-sqlite3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 12:46:02 +08:00
tommy0103 905c10789a refactor(app): migrate to ESM; preload emitted as CJS for sandbox (Phase 5d-3b)
App source (main/preload/worker/renderer) -> ESM; app is now type: module;
__dirname via import.meta.url; worker spawned with type module. Preload is built
as CJS (electron-vite output format) because the sandboxed renderer does not
support ESM preload; main loads ../preload/index.js. Removed dead imports
(nativeImage, readline) and the obsolete scripts/dev.js.

Tests: 4 app tests require->import; app-main-settings rewritten with node:test
mock.module + dynamic import (replacing CJS Module._load mocking); test script
adds --experimental-test-module-mocks. electron-vite build clean, 119/119, and
npm run dev verified: app launches, preload bridges IPC, data loads.
2026-07-09 11:05:00 +08:00
tommy0103 80c125a572 build(app): migrate to electron-vite (main/preload/renderer), keep electron-builder (Phase 5d-3a)
Restructure app into src/{main,preload,renderer}; electron.vite.config.ts builds
all three (each main module its own input so CJS requires + the indexer worker
resolve; better-sqlite3 externalized; Vue plugin for renderer). main/index.js
paths updated for the out/ layout + ELECTRON_RENDERER_URL. Still JS/CJS — TS+ESM
and core consumption are the next stages. Verified: npm run dev launches clean;
electron-vite build succeeds; root suite 119/119.
2026-07-09 10:20:18 +08:00
tommy0103 1a34245618 chore: sanitize tests, add CONTEXT.md + ADRs, track tests/docs 2026-07-08 16:11:12 +08:00