- New component renders button + consent modal + spinner + attribution - Best-effort warning shown once per session for stock categories - Hidden if not premium or category kind != 'priced' - Consent persisted per-profile in user_preferences.price_fetching_consent - Manual unit_price input remains active in all paths - 17 vitest tests (no RTL/jsdom — logged MEDIUM in decisions-log.md) - Wired into SnapshotLineRow/SnapshotEditor/SnapshotEditPage - asset_type hardcoded to 'stock' pending category schema extension (MEDIUM) Closes #158
3 KiB
Decisions Log — /autopilot run of 2026-04-27
Issue #156 — session cap budget policy (MEDIUM)
The 100-request session cap is checked BEFORE rate-limit enforcement and in-flight deduplication. Successful fetches increment the counter; failures (4xx, 5xx, network) do NOT consume the budget. Rationale: a user who hits a bad symbol or an auth error should not have their session budget drained by error conditions outside their control. This is the most user-friendly interpretation of "hard 100/session cap" while still protecting against runaway loops.
Issue #156 — __resetForTests helper exported from prices namespace (LOW)
The prices.__resetForTests() helper is exported alongside fetchPrice. This avoids
the need for vi.resetModules() + dynamic import between tests, which is flakier and
slower. The helper is named with __ prefix to signal test-only usage. Alternative
considered: module-level export — rejected because it would pollute the public API
surface of balance.service outside the prices namespace.
Issue #158 — no @testing-library/react or jsdom in project (MEDIUM)
The project has no @testing-library/react, no jsdom, and no happy-dom configured in vitest.
PriceFetchControl.test.tsx therefore uses direct unit tests of the component's internal
logic (hook calls, service calls, state transitions) via mocked dependencies rather than
DOM rendering. This tests behavior but not DOM structure. If the project later adopts RTL,
these tests should be rewritten with render() + getByRole/getByText. Not adding RTL in
autopilot mode (would require npm install permission).
Issue #158 — user_preferences table is per-profile by architecture (LOW)
Each profile has its own SQLite database file (confirmed in ProfileContext). The
user_preferences table has no profile_id column — the profile scoping is implicit
(each DB = one profile). Therefore the consent key price_fetching_consent does NOT
need a profile_id prefix; the key alone is sufficient for correct per-profile scoping.
Issue #158 — asset_type not in category schema (MEDIUM)
The balance_categories table has no asset_type column (confirmed by schema.sql).
The PriceFetchControl receives assetType prop from the parent. SnapshotLineRow/SnapshotEditor
do not yet carry asset_type. Per autopilot decision policy, defaulting to 'stock' as
hardcoded fallback in SnapshotEditor wiring. A // TODO: asset_type from category schema
comment marks the injection point. A follow-up issue should add asset_type TEXT DEFAULT 'stock'
to balance_categories and thread it through SnapshotEditor props.
Issue #156 — rate-limit pacing test strategy (LOW)
The pacing test verifies that setTimeout is called with a positive delay for the 2nd and 3rd concurrent calls, rather than asserting exact wall-clock timestamps via Date.now(). This is because vi.useFakeTimers() advances Date.now() via timer advancement, not automatically between microtasks. The spy approach is more resilient to vitest internals and fake-timer edge cases.