Files
gacha-event-tracker/docs/superpowers/plans/2026-11-01-content-classification.md
T
Lucas Winther b03a6ed2d1 feat(reader): separate Miliastra and track passes as cycles
Route source-classified activities into an independent Miliastra lane and
show dated battle passes once under Cycles. Genshin's About tab can disable
TCG tracking without deleting progress or history.

Activate routing and presentation together so no intermediate version moves
saved progress while still rendering its old owner. Preserve identities,
clock revisions and deletion timestamps across late writes and backups.
2026-11-01 04:12:14 +01:00

10 KiB

Content Classification Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [x]) syntax for tracking.

Goal: Separate Miliastra, represent sourced passes as cycles, and let readers disable Genshin TCG tracking without losing state.

Architecture: Preserve source/identity game and IDs in the feed; optional classification metadata supplies the destination lane, battle-pass family and TCG tag. A shared resolver handles old feeds and saved snapshots. Reader normalization routes identified Miliastra state atomically and preserves account clocks; pass presentation reuses event progress rather than creating another task store.

Tech Stack: Bun, TypeScript, React 19, Zod; existing dependencies only.

Spec: docs/superpowers/specs/2026-11-01-content-classification-design.md

Global Constraints

  • Work on main in this checkout per AGENTS.md. No commits or pushes without request.
  • Keep every event ID, date, source ID and source URL unchanged.
  • Preserve old feed validation: classification is additive; wire game stays the identity game.
  • Miliastra arrives off; TCG defaults on across Genshin accounts.
  • No guessed recurrence, dates, classification or completion; reader-created content is excluded.

Review Focus

  • Legacy imports/late writes after a move must honor destination deletion timestamps.
  • Multiple accounts with equal regions must retain distinct ownership and clock history.
  • Old cached feeds and remembered snapshots must classify consistently without causing startup failure.
  • Pass-only tracking and independent pins must not create duplicate rows or unrelated cycle follows.
  • Hidden TCG must leave history intact and disappear from all live instructions, including event dailies.

Task 1: Shared classification and parser evidence

Files: src/shared/schema.ts, src/shared/classification.ts (new), src/shared/games.ts, src/ingest/parsers/{fandom,fandomTable,versionTable}.ts, src/ingest/merge.ts, test/classification.test.ts (new), affected adapter expectations.

Interfaces: classificationFor(event): EventClassification, displayGameFor(event): string, passFamilyFor(event): string | null, isTcgEvent(event): boolean. Metadata is optional { lane?: "miliastra", activity?: "battle-pass", family?: string, tags?: string[] }. Recognize established article families and source registrations for cached feeds; parser reads stated type labels. Register miliastra for current clients but keep existing wire game fields unchanged.

  • Write tests for source categories, cached source paths, custom exclusion, source winner lacking metadata, and unknown fallback; run and observe failure.
  • Add schema/resolver, parser labels and merge retention; preserve fixture IDs and dates. Cite reviewed TCG family evidence.
  • Run bun test test/classification.test.ts test/merge.test.ts test/adapters/game8.test.ts test/adapters/fandomTable.test.ts test/adapters/versionTable.test.ts; regenerate only intentionally changed expectation fields.

Task 2: Atomic routing and compatibility

Files: src/client/state/reader/contentRouting.ts (new), store.ts, reducer.ts, export.ts, schema.ts as needed; test/reader-content-routing.test.ts (new).

Interfaces: routeReaderContent(state: ReaderState): ReaderState is pure and idempotent, driven by the shared classifier. A persisted contentRoutes record maps original accounts to Miliastra counterparts. Counterpart identity is derived deterministically from the existing account ID; it is not a reminted identity. Copy original clock revisions and unknown account fields, preserve retired status, and append Miliastra to known games while hiding it on first introduction.

Rebase account-scoped fieldTimes paths for classified IDs, preserving the latest timestamp at either path and retaining origin tombstones. Route incoming old-account actions before conflict checks. Destination values win timestamp ties; progress fields merge by latest field time, day logs by union subject to day tombstones, amounts by period timestamp, ignores by saved timestamps. Archive/seen records keep original clock/resolved end and event ID but use logical game Miliastra. Do not reuse an unrelated same-region account. Normalize startup, dispatch and imports/restores so old-tab/backup deltas route consistently. No source-state transfer is repeated after clearing a mark.

  • Add failing tests for two same-region origins, clock revisions, all record collections, idempotence, source/destination clears, late actions and import/restore.
  • Implement pure normalization and wire locked save/import paths; create empty destination data only for actual moved state or explicit account use.
  • Run bun test test/reader-content-routing.test.ts and existing reader store/export/migration tests; expect no state validation failures.

Task 3: Classified reader rows and Miliastra settings

Files: src/client/state/reader/{eventRows,routines,migration,onboarding}.ts, src/client/ReaderApp.tsx, src/client/components/redesign/{GameSettings,SetupWizard}.tsx, src/shared/feed.ts, tests for reader rows and views.

Interfaces: Reader event rows use logical destination game and existing IDs. Shared category filtering excludes TCG when prefs.genshinTcg === false. Retain classified snapshots for migration and source-backed pass rows even when ordinary Events is off. Freshness/source lookup maps logical Miliastra to supplying Genshin sources.

  • Add failing tests for lane independence, hidden-lane policy, cached snapshots, source attribution and unchanged Genshin rows.
  • Route row rendering and lane discovery; offer Miliastra independently without synthetic dailies; point to its settings from Genshin About.
  • Run focused reader/view tests and typecheck.

Task 4: Feed-backed battle-pass cycles

Files: src/client/state/reader/{routines,contentRouting}.ts, src/client/components/redesign/{RoutinesPage,RoutineDrawer,RoutineProgress,TimelinePage,GameSettings,SetupWizard}.tsx, src/client/ReaderApp.tsx, test/reader-pass-cycles.test.ts (new).

Interfaces: Feed cycle rows carry event and a stable family placement key while retaining per-window event ID for progress. Family placements are persisted in account.routines; next window inherits pins, not completion. Existing event followers gain pass-family placements once, without following unrelated routines. New accounts derive pass following from Cycles intent. Event-backed routine controls dispatch existing event outcome/progress actions.

  • Add failing tests for old-reader transition, pass-only tracking, next-window completion isolation, independent pins, Miliastra pass off until lane enabled, and no second ordinary-event row.
  • Implement family placements and dynamic rows using sourced event clocks; extend library, progress controls, checklist guidance and timeline windows. Never render a predicted recurrence/reset.
  • Run bun test test/reader-pass-cycles.test.ts plus affected routine/timeline tests and typecheck.

Task 5: Genshin TCG switch

Files: src/client/components/redesign/GameSettings.tsx, src/client/state/reader/eventRows.ts, routines.ts, test/reader-tcg.test.tsx (new).

Interfaces: prefs.genshinTcg absent means true. Genshin About renders Track TCG events, supporting copy from spec, immediate save and failure rollback. All live surfaces use the shared reader classification predicate; history remains readable.

  • Add failing tests for default/on/off/cached snapshot behavior, multiple accounts, saved-history preservation and rejected save.
  • Implement existing-style accessible toggle and shared filtering; no ignored/completed writes.
  • Run focused TCG and reader row tests.

Task 6: Verification and documentation

Files: PRD, DATA-MODEL, INGESTION, DESIGN, ARCHITECTURE where changed; prior pass spec and audit; focused expiration regressions; browser scratch files only under /tmp.

  • Verify audited ended trials cannot re-enter live rows through remembered state; verify regional day-precision ends without changing stored timestamps. Test existing behavior before proposing repairs.
  • Compare parsed/built before/after IDs and dates offline. Update docs for implemented classification, routing and cycle behavior; record any plan rulings in the ledger.
  • Run bun run typecheck, full bun test, git diff --check. Build only css/js/static shell; use a separately generated temporary feed if needed for deterministic browser data.
  • Browser-check desktop/mobile, both themes, ordinary HTTP hostname startup/writes, old-state migration, TCG off/reload/on, pass-only and Miliastra isolation. Run Impeccable detector once over changed UI files.
  • Fresh-context final review; fix material findings with regression tests; report checks and any remaining limitations. Leave work uncommitted per AGENTS.md.

Verification record

Completed 2026-11-01. Full suite: 1748 pass, 0 fail; typecheck and diff check pass. The baseline required HTTP test binding outside the sandbox. Browser checks used Chrome on an ordinary HTTP hostname at fresh build origins: migration, TCG off/reload/failure/on, pass completion/undo, Miliastra enabling and retained pass following, new-reader startup, and desktop/mobile dark/light layouts with no page errors or horizontal overflow. Impeccable detector returned no findings. Offline comparison against HEAD covered 25 sources / 197 parsed rows and every merged game: IDs, dates, titles and source identities unchanged; only category metadata changed. The old feed schema accepts the new parsed output. Fresh code review produced five material findings, all fixed with failing-then- passing regressions (backup tombstones, legacy ignores, late-created pass following, inventory precedence and unrelated imported origins). Additional regressions cover the new-reader placeholder and already-routed import mapping. Implemented on main. The follow-up changelog commit records the implementation range under the repository convention.