Fix dashboard hydration and archive theming
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-23
|
||||
@@ -0,0 +1,73 @@
|
||||
## Context
|
||||
|
||||
The repository starts with a product plan and OpenSpec configuration but no application code. The first version needs a complete local-first implementation using `Next.js`, `Prisma`, `SQLite`, and a fully offline local LLM runtime, while keeping scope intentionally narrow: one user, manual data entry, fixed categories, merchant-assisted categorization, and dashboard-only insights. Month boundaries are based on the local machine timezone, which affects date parsing, monthly aggregation, and paycheck coverage calculations.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Build a single deployable `Next.js` app with UI views and server routes in one codebase.
|
||||
- Persist expenses, paychecks, and generated monthly insights in a local SQLite database managed by Prisma.
|
||||
- Centralize monthly aggregation logic so dashboard reads and AI generation use the same numbers.
|
||||
- Keep AI integration isolated behind a small service layer that prepares structured monthly context and calls a fully offline local inference runtime.
|
||||
- Make v1 testable with deterministic validation, aggregation, and safe fallback behavior for sparse data.
|
||||
- Add privacy-preserving merchant category suggestion with deterministic merchant mappings before model inference.
|
||||
|
||||
**Non-Goals:**
|
||||
- Authentication, multi-user support, bank sync, receipt scanning, background jobs, or email delivery.
|
||||
- Fully automatic uncapped categorization without user review for ambiguous merchants, editing data through AI, or free-form custom categories in v1.
|
||||
- Complex financial forecasting beyond simple next-month guidance derived from recent activity.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Use a single `Next.js` app for UI and APIs
|
||||
- Rationale: the project is small, local-first, and benefits from one codebase for pages, route handlers, and shared utilities.
|
||||
- Alternative considered: separate frontend and API service. Rejected because it adds deployment and data-sharing complexity without helping the v1 scope.
|
||||
|
||||
### Use Prisma with SQLite for persistence
|
||||
- Rationale: Prisma provides schema management, typed queries, and straightforward migrations while keeping SQLite as a simple embedded database.
|
||||
- Alternative considered: raw SQLite queries. Rejected because it slows down schema evolution and validation during initial development.
|
||||
|
||||
### Store money as integer cents and dates as local calendar strings
|
||||
- Rationale: integer cents avoid floating-point issues, and local-date strings such as `YYYY-MM-DD` align with the local machine timezone requirement for monthly boundaries.
|
||||
- Alternative considered: floating-point amounts or UTC timestamps only. Rejected because both introduce avoidable ambiguity for monthly reporting.
|
||||
|
||||
### Put aggregation logic in shared server-side services
|
||||
- Rationale: dashboard totals, paycheck coverage, category breakdowns, and AI snapshots must stay consistent across endpoints.
|
||||
- Alternative considered: separate logic per route. Rejected because it risks drift between dashboard and insight generation.
|
||||
|
||||
### Use `Ollama` with a local Qwen-class instruct model
|
||||
- Rationale: privacy is a primary product requirement, and the target machine can comfortably run a recent local model for lightweight categorization and summary generation.
|
||||
- Alternative considered: hosted `OpenAI`. Rejected because it violates the privacy-first goal for personal financial data.
|
||||
|
||||
### Add an AI service boundary with structured prompt input and fallback responses
|
||||
- Rationale: the app needs runtime isolation, predictable prompt shape, and safe messaging when local inference is unavailable or data is too sparse for useful advice.
|
||||
- Alternative considered: calling the local model directly from a route handler with raw records. Rejected because it couples prompting, aggregation, and transport too tightly.
|
||||
|
||||
### Use merchant rules first and local-model fallback second for category suggestion
|
||||
- Rationale: most repeated merchants can be categorized deterministically and faster than model inference, while unknown merchants still benefit from local AI assistance.
|
||||
- Alternative considered: model-only categorization. Rejected because it is slower, less predictable, and unnecessary for common merchants.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Local timezone handling differs by machine] -> Normalize month calculations around stored local-date strings and test month edges explicitly.
|
||||
- [SQLite limits concurrency] -> Acceptable for single-user local-first v1; no mitigation beyond keeping writes simple.
|
||||
- [AI output quality varies with sparse or noisy data] -> Add minimum-data fallback logic and keep prompts grounded in structured aggregates.
|
||||
- [Local model may be unavailable or not yet pulled] -> Detect runtime/model readiness and return explicit offline setup guidance in the UI/API.
|
||||
- [Merchant names can be ambiguous] -> Use auto-fill only for known deterministic mappings and require user confirmation for fallback suggestions.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Scaffold the `Next.js` app and install core dependencies.
|
||||
2. Add the Prisma schema, create the initial SQLite migration, and generate the client.
|
||||
3. Implement CRUD routes and UI forms for expenses and paychecks.
|
||||
4. Implement dashboard aggregation and month filtering.
|
||||
5. Add the offline AI service, merchant-category suggestion flow, and persistence for generated monthly insights.
|
||||
6. Run automated tests, then exercise the main flows in the browser.
|
||||
|
||||
Rollback is straightforward in early development: revert the code change and reset the local SQLite database if schema changes become invalid.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Which exact local Qwen model tag should be the initial default in `Ollama`?
|
||||
- Should generated monthly insights overwrite prior insights for the same month or create a historical trail of regenerated summaries?
|
||||
- Do we want soft confirmation in the UI before deleting expenses or paychecks, or is immediate deletion acceptable for v1?
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
The project currently has a product plan but no runnable application, spec artifacts, or implementation scaffold. Formalizing the first version now creates a clear contract for building a local-first expense tracker with reliable monthly summaries, private offline AI assistance, and no dependency on hosted model providers.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a local-first web app for tracking expenses and biweekly paychecks without authentication.
|
||||
- Add dashboard capabilities for month-to-date totals, category breakdowns, cash flow, and spending comparisons.
|
||||
- Add fully offline AI insight generation for a selected month using structured aggregates and transaction samples.
|
||||
- Add merchant-name-based category suggestion using deterministic rules plus local-model fallback.
|
||||
- Add local persistence, validation, and API routes for expenses, paychecks, dashboard data, and insight generation.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `expense-tracking`: Record, list, and delete categorized expenses for a given date.
|
||||
- `paycheck-tracking`: Record, list, and delete paycheck entries based on actual pay dates.
|
||||
- `monthly-dashboard`: View month-specific spending, income, and derived financial summaries.
|
||||
- `monthly-insights`: Generate private offline AI insights from monthly financial activity.
|
||||
- `category-suggestion`: Suggest expense categories from merchant/shop names without cloud calls.
|
||||
|
||||
### Modified Capabilities
|
||||
- None.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected code: new `Next.js` application, server routes, UI views, Prisma schema, and AI integration service.
|
||||
- APIs: `POST/GET/DELETE` routes for expenses and paychecks, `GET /dashboard`, and `POST /insights/generate`.
|
||||
- Dependencies: `Next.js`, `Prisma`, `SQLite`, `Ollama`, and a local Qwen-class instruct model.
|
||||
- Systems: local machine timezone handling for month boundaries and persisted local database storage.
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: System suggests categories from merchant names
|
||||
The system SHALL support merchant-name-based category suggestion for expense entry while keeping all suggestion logic fully offline.
|
||||
|
||||
#### Scenario: Known merchant resolves from deterministic rules
|
||||
- **WHEN** the user enters a merchant or shop name that matches a known merchant rule
|
||||
- **THEN** the system assigns the mapped category without needing model inference
|
||||
|
||||
#### Scenario: Unknown merchant falls back to local model
|
||||
- **WHEN** the user enters a merchant or shop name that does not match a known merchant rule
|
||||
- **THEN** the system asks the local AI service for a category suggestion and returns the suggested category
|
||||
|
||||
### Requirement: Ambiguous suggestions remain user-controlled
|
||||
The system SHALL keep the final saved category under user control for ambiguous or model-generated suggestions.
|
||||
|
||||
#### Scenario: User confirms model suggestion before save
|
||||
- **WHEN** the category suggestion comes from model inference instead of a deterministic rule
|
||||
- **THEN** the user can review and confirm or change the category before the expense is saved
|
||||
|
||||
#### Scenario: No cloud fallback is used
|
||||
- **WHEN** the local suggestion service is unavailable
|
||||
- **THEN** the system continues to allow manual category selection and does not send merchant data to a hosted provider
|
||||
@@ -0,0 +1,30 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can record categorized expenses
|
||||
The system SHALL allow the user to create an expense with a title, amount, category, and local calendar date using fixed starter categories.
|
||||
|
||||
#### Scenario: Valid expense is created
|
||||
- **WHEN** the user submits a title, positive amount, valid category, and valid local date
|
||||
- **THEN** the system stores the expense and returns the created record
|
||||
|
||||
#### Scenario: Invalid expense is rejected
|
||||
- **WHEN** the user submits a missing title, invalid amount, invalid category, or invalid date
|
||||
- **THEN** the system rejects the request with a validation error and does not store the expense
|
||||
|
||||
### Requirement: User can review and delete expenses
|
||||
The system SHALL allow the user to list recorded expenses and delete a specific expense by identifier.
|
||||
|
||||
#### Scenario: Expenses are listed
|
||||
- **WHEN** the user requests expenses for the app
|
||||
- **THEN** the system returns stored expenses in a stable order with their recorded fields
|
||||
|
||||
### Requirement: User can browse expense history by month
|
||||
The system SHALL allow the user to select a `YYYY-MM` month when reviewing expense history and SHALL return the expenses recorded for that month.
|
||||
|
||||
#### Scenario: Prior month entries are visible
|
||||
- **WHEN** the user selects February 2026 in the add-expense history view
|
||||
- **THEN** the system shows the expenses recorded in February 2026 and exposes delete actions for deletable entries in that month
|
||||
|
||||
#### Scenario: Expense is deleted
|
||||
- **WHEN** the user deletes an existing expense
|
||||
- **THEN** the system removes that expense and it no longer appears in future listings or aggregates
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Dashboard shows month-specific financial totals
|
||||
The system SHALL return month-specific dashboard data for a requested `YYYY-MM` month using the local machine timezone for month boundaries.
|
||||
|
||||
#### Scenario: Dashboard totals are calculated for a populated month
|
||||
- **WHEN** the user requests the dashboard for a month with expenses and paychecks
|
||||
- **THEN** the system returns total expenses, total paychecks, net cash flow, and a category breakdown for that month
|
||||
|
||||
#### Scenario: Dashboard supports partial current-month data
|
||||
- **WHEN** the user requests the dashboard for the current month before the month is complete
|
||||
- **THEN** the system returns meaningful month-to-date totals and comparisons using the transactions recorded so far
|
||||
|
||||
### Requirement: Dashboard includes derived spending comparisons
|
||||
The system SHALL provide derived comparisons for the selected month, including highest category, largest expense, average daily spend, and paycheck coverage information.
|
||||
|
||||
#### Scenario: Derived comparisons are available
|
||||
- **WHEN** the selected month contains enough data for comparisons
|
||||
- **THEN** the system returns the highest category, largest single expense, average daily spend, and spend-versus-paycheck coverage values
|
||||
|
||||
#### Scenario: Derived comparisons degrade safely for sparse data
|
||||
- **WHEN** the selected month has no expenses or otherwise insufficient data for a comparison
|
||||
- **THEN** the system returns null or empty-safe comparison fields instead of failing
|
||||
@@ -0,0 +1,30 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can generate monthly AI insights on demand
|
||||
The system SHALL allow the user to manually generate monthly AI insights for any month with existing or sparse data by sending structured monthly context to a fully offline local inference runtime.
|
||||
|
||||
#### Scenario: Insights are generated for a month with data
|
||||
- **WHEN** the user requests insight generation for a month with recorded activity
|
||||
- **THEN** the system sends monthly aggregates plus transaction samples to the local AI service and returns a rendered narrative summary with structured supporting totals
|
||||
|
||||
#### Scenario: Prior month insights can be generated
|
||||
- **WHEN** the user requests insight generation for a previous month that has recorded data
|
||||
- **THEN** the system generates and stores insight output for that requested month
|
||||
|
||||
### Requirement: Insight generation is read-only and safe for sparse months
|
||||
The system SHALL keep AI insight generation read-only and return a safe fallback summary when a month does not have enough data for meaningful guidance.
|
||||
|
||||
#### Scenario: Sparse month returns fallback insight
|
||||
- **WHEN** the user requests insight generation for a month with empty or near-empty data
|
||||
- **THEN** the system returns a fallback message instead of low-confidence advice
|
||||
|
||||
#### Scenario: AI does not mutate financial records
|
||||
- **WHEN** the system generates or stores monthly insights
|
||||
- **THEN** no expense or paycheck records are created, updated, or deleted as part of that request
|
||||
|
||||
### Requirement: Insight generation remains private and resilient offline
|
||||
The system SHALL keep monthly insight generation fully offline and provide a clear fallback response when the local model runtime or selected model is unavailable.
|
||||
|
||||
#### Scenario: Local runtime is unavailable
|
||||
- **WHEN** the user requests monthly insights while the local AI runtime is not running or the configured model is unavailable
|
||||
- **THEN** the system returns a clear setup or availability message instead of attempting a cloud fallback
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can record paychecks by pay date
|
||||
The system SHALL allow the user to create a paycheck with a positive amount and a local pay date.
|
||||
|
||||
#### Scenario: Valid paycheck is created
|
||||
- **WHEN** the user submits a positive amount and valid local pay date
|
||||
- **THEN** the system stores the paycheck and returns the created record
|
||||
|
||||
#### Scenario: Invalid paycheck is rejected
|
||||
- **WHEN** the user submits a missing or invalid amount or date
|
||||
- **THEN** the system rejects the request with a validation error and does not store the paycheck
|
||||
|
||||
### Requirement: User can review and delete paychecks
|
||||
The system SHALL allow the user to list recorded paychecks and delete a specific paycheck by identifier.
|
||||
|
||||
#### Scenario: Paychecks are listed
|
||||
- **WHEN** the user requests paychecks for the app
|
||||
- **THEN** the system returns stored paychecks in a stable order with their recorded fields
|
||||
|
||||
#### Scenario: Paycheck is deleted
|
||||
- **WHEN** the user deletes an existing paycheck
|
||||
- **THEN** the system removes that paycheck and it no longer appears in future dashboard totals or insight inputs
|
||||
@@ -0,0 +1,38 @@
|
||||
## 1. Project setup
|
||||
|
||||
- [x] 1.1 Scaffold the `Next.js` app with TypeScript, linting, and baseline project configuration.
|
||||
- [x] 1.2 Add runtime dependencies for Prisma, SQLite, validation, charts, and offline AI integration.
|
||||
- [x] 1.3 Add development dependencies and scripts for testing, Prisma generation, and local development.
|
||||
- [x] 1.4 Add base environment and ignore-file setup for local database and API key configuration.
|
||||
|
||||
## 2. Persistence and shared services
|
||||
|
||||
- [x] 2.1 Define Prisma models for `Expense`, `Paycheck`, and `MonthlyInsight` and create the initial SQLite migration.
|
||||
- [x] 2.2 Implement shared validation schemas for expenses, paychecks, and month query parameters.
|
||||
- [x] 2.3 Implement shared money and local-date utilities for month boundary calculations.
|
||||
|
||||
## 3. Expense and paycheck workflows
|
||||
|
||||
- [x] 3.1 Implement expense API routes for create, list, and delete operations.
|
||||
- [x] 3.2 Implement paycheck API routes for create, list, and delete operations.
|
||||
- [x] 3.3 Build the `Add Expense` view with form submission, validation feedback, and expense listing.
|
||||
- [x] 3.4 Build the `Income/Paychecks` view with form submission, validation feedback, and paycheck listing.
|
||||
|
||||
## 4. Dashboard and insights
|
||||
|
||||
- [x] 4.1 Implement monthly dashboard aggregation services for totals, category breakdowns, and derived comparisons.
|
||||
- [x] 4.2 Implement the dashboard API route and render dashboard sections for month-to-date metrics and comparisons.
|
||||
- [x] 4.3 Implement the offline `Ollama` insight service with structured monthly snapshot input and sparse-month fallback logic.
|
||||
- [x] 4.4 Implement insight generation and display in the dashboard, including persisted monthly insight records and offline-runtime fallback messaging.
|
||||
|
||||
## 5. Offline categorization
|
||||
|
||||
- [x] 5.1 Implement deterministic merchant-to-category mapping for known merchants.
|
||||
- [x] 5.2 Implement a local-model category suggestion endpoint for unknown merchants.
|
||||
- [x] 5.3 Update the expense entry flow to auto-fill known merchants and require confirmation for model-generated suggestions.
|
||||
- [x] 5.4 Add local runtime availability handling so category suggestion falls back to manual selection without cloud calls.
|
||||
|
||||
## 6. Verification
|
||||
|
||||
- [x] 6.1 Add automated tests for validation, persistence, dashboard aggregates, offline insight fallback behavior, and category suggestion rules.
|
||||
- [x] 6.2 Verify the primary user flows in the browser, including expense entry, paycheck entry, dashboard updates, category suggestion, and insight generation.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-23
|
||||
@@ -0,0 +1,54 @@
|
||||
## Context
|
||||
|
||||
The project uses Next.js with Tailwind CSS v4 and a warm stone/amber palette. All styling is done via Tailwind utility classes directly in JSX. There are no separate CSS modules for components. The root layout owns the `<html>` and `<body>` tags, making it the natural place to manage the theme class and prevent flash-of-unstyled-content.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Implement class-based dark mode that works with Tailwind v4's `@custom-variant` API.
|
||||
- Prevent theme flash on page load with an inline blocking script.
|
||||
- Persist user preference in `localStorage` under the key `theme`.
|
||||
- Fall back to system preference (`prefers-color-scheme`) when no preference is saved.
|
||||
- Keep the dark palette warm and consistent with the existing amber/stone design language.
|
||||
|
||||
**Non-Goals:**
|
||||
- Server-side theme rendering or cookies (local-only app, no SSR theme concerns beyond flash prevention).
|
||||
- Per-page or per-component theme overrides.
|
||||
- Animated theme transitions beyond the existing `transition-duration: 180ms` on interactive elements.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Use Tailwind v4 `@custom-variant` for class-based dark mode
|
||||
- Rationale: Tailwind v4's default dark mode is media-query-based. Adding `@custom-variant dark (&:where(.dark, .dark *))` in `globals.css` enables the `dark:` prefix to respond to a class on any ancestor, which is the standard pattern for toggle-able dark mode.
|
||||
- Alternative considered: Continue using media-query dark mode with no toggle. Rejected because users cannot override system preference.
|
||||
|
||||
### Store theme preference in `localStorage`
|
||||
- Rationale: Simple, synchronous, requires no server or cookies. Reads correctly in the blocking inline script.
|
||||
- Alternative considered: Cookies for SSR. Rejected because this app is local-first and has no meaningful SSR theme benefit.
|
||||
|
||||
### Inject an inline blocking script in `<head>` to set the `dark` class before first paint
|
||||
- Rationale: Prevents the flash where the page briefly renders in light mode before React hydrates and reads `localStorage`. The script is small and runs synchronously.
|
||||
- Alternative considered: Set the class in a React `useEffect`. Rejected because `useEffect` runs after paint, causing a visible flash.
|
||||
|
||||
### Warm deep-stone dark palette
|
||||
- Rationale: The light theme is built on warm stone and amber tones. The dark theme mirrors this with deep stone backgrounds (`stone-950`, `stone-900`, `stone-800`) and dimmed amber/emerald accents, keeping the visual identity coherent.
|
||||
- Alternative considered: Neutral dark greys. Rejected because they clash with the warm amber accents and feel disconnected from the brand.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Inline script adds a small amount of render-blocking HTML] → Acceptable; the script is under 200 bytes and only runs once per page load.
|
||||
- [LocalStorage is unavailable in some privacy modes] → The script wraps the read in a try/catch and falls back to system preference.
|
||||
- [Many components have hardcoded warm background strings like `bg-[#fffaf2]`] → These are replaced with equivalent Tailwind tokens plus `dark:` overrides so the mapping is explicit and maintainable.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Update `globals.css` with `@custom-variant dark` and dark-mode CSS variable overrides.
|
||||
2. Create the `ThemeToggle` client component.
|
||||
3. Update `layout.tsx` to add the blocking script and render `ThemeToggle` in the header.
|
||||
4. Update `site-nav.tsx` with dark-mode nav styles.
|
||||
5. Update `home-dashboard.tsx`, `expense-workspace.tsx`, `paycheck-workspace.tsx`, and `recurring-expense-manager.tsx` with `dark:` variants.
|
||||
6. Update page-level header text that uses hardcoded colour classes.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should the toggle also expose a "System" option (three-way: Light / Dark / System) rather than a binary flip? Deferred to a follow-up; the initialisation script already handles system fallback on first visit.
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
The app currently ships with a warm light theme only. Adding a system-aware dark mode with a manual toggle removes eye strain in low-light conditions and meets baseline accessibility expectations for a personal finance tool used at any hour.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a class-based dark mode variant using Tailwind v4 `@custom-variant` so all `dark:` utilities are controlled by a `dark` class on `<html>`.
|
||||
- Add a `ThemeToggle` component that persists the user's preference in `localStorage` and initialises the correct class before first paint to prevent flash.
|
||||
- Update the root layout to inject the inline initialisation script and render the toggle inside the existing header.
|
||||
- Apply `dark:` variants to all UI components to produce a warm, deep-stone dark palette consistent with the existing amber/stone design language.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `dark-mode`: Users can switch between light and dark themes with a toggle that persists across sessions. System preference is respected on first visit.
|
||||
|
||||
### Modified Capabilities
|
||||
- `expense-tracking`: Expense workspace and recurring expense manager now render correctly in both themes.
|
||||
- `paycheck-tracking`: Paycheck workspace now renders correctly in both themes.
|
||||
- `monthly-dashboard`: Dashboard sections, insight cards, category bars, and stat cards now render correctly in both themes.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected code: `globals.css`, `layout.tsx`, all components under `src/components/`, and page header text in `src/app/`.
|
||||
- APIs: None.
|
||||
- Dependencies: None — uses Tailwind v4 built-ins and the Web Storage API.
|
||||
- Systems: No server-side changes required; theme state is fully client-side.
|
||||
@@ -0,0 +1,26 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can switch between light and dark themes
|
||||
The system SHALL allow the user to toggle between light and dark themes with a persistent preference.
|
||||
|
||||
#### Scenario: User toggles dark mode on
|
||||
- **WHEN** the user activates the theme toggle while the app is in light mode
|
||||
- **THEN** the system applies the dark theme and saves the preference for future visits
|
||||
|
||||
#### Scenario: User toggles dark mode off
|
||||
- **WHEN** the user activates the theme toggle while the app is in dark mode
|
||||
- **THEN** the system applies the light theme and saves the preference for future visits
|
||||
|
||||
### Requirement: Theme preference respects system defaults on first visit
|
||||
The system SHALL use the user's system color scheme preference when no saved preference exists.
|
||||
|
||||
#### Scenario: No stored preference uses system theme
|
||||
- **WHEN** the user opens the app for the first time without a saved theme preference
|
||||
- **THEN** the system applies dark mode when the operating system prefers dark color schemes and light mode otherwise
|
||||
|
||||
### Requirement: Theme selection loads without a flash of the wrong theme
|
||||
The system SHALL initialize the theme before the first visible paint so the page does not briefly render in the wrong theme.
|
||||
|
||||
#### Scenario: Initial paint matches saved theme
|
||||
- **WHEN** the app loads and a saved theme preference exists
|
||||
- **THEN** the document theme is applied before the page content is visibly rendered
|
||||
@@ -0,0 +1,12 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Expense tracking UI renders correctly in both themes
|
||||
The system SHALL render the expense tracking workspace, history list, forms, and item states with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Expense workspace renders in dark mode
|
||||
- **WHEN** the user opens the expense tracking view while dark mode is active
|
||||
- **THEN** the form card, history card, inputs, actions, and expense rows use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Expense workspace renders in light mode
|
||||
- **WHEN** the user opens the expense tracking view while light mode is active
|
||||
- **THEN** the form card, history card, inputs, actions, and expense rows use light-compatible colors and remain readable
|
||||
@@ -0,0 +1,12 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Monthly dashboard UI renders correctly in both themes
|
||||
The system SHALL render dashboard sections, insight cards, category bars, stat tiles, and empty states with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Dashboard renders in dark mode
|
||||
- **WHEN** the user opens the dashboard while dark mode is active
|
||||
- **THEN** the summary cards, comparison cards, progress bars, and empty states use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Dashboard renders in light mode
|
||||
- **WHEN** the user opens the dashboard while light mode is active
|
||||
- **THEN** the summary cards, comparison cards, progress bars, and empty states use light-compatible colors and remain readable
|
||||
@@ -0,0 +1,12 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Paycheck tracking UI renders correctly in both themes
|
||||
The system SHALL render the paycheck tracking workspace, schedule panel, form, and list items with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Paycheck workspace renders in dark mode
|
||||
- **WHEN** the user opens the paycheck tracking view while dark mode is active
|
||||
- **THEN** the schedule panel, form card, inputs, and paycheck rows use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Paycheck workspace renders in light mode
|
||||
- **WHEN** the user opens the paycheck tracking view while light mode is active
|
||||
- **THEN** the schedule panel, form card, inputs, and paycheck rows use light-compatible colors and remain readable
|
||||
@@ -0,0 +1,25 @@
|
||||
## 1. CSS and variant setup
|
||||
|
||||
- [x] 1.1 Add `@custom-variant dark (&:where(.dark, .dark *))` to `globals.css` and update `:root` / `@theme` blocks with dark-mode CSS variable overrides.
|
||||
|
||||
## 2. Theme toggle infrastructure
|
||||
|
||||
- [x] 2.1 Create `src/components/theme-toggle.tsx` — a client component that reads and writes `localStorage` theme preference, toggles the `dark` class on `<html>`, and renders a sun/moon button.
|
||||
- [x] 2.2 Update `src/app/layout.tsx` to inject the inline blocking script in `<head>` and render `ThemeToggle` in the header alongside `SiteNav`.
|
||||
|
||||
## 3. Component dark-mode styles
|
||||
|
||||
- [x] 3.1 Update `src/components/site-nav.tsx` with `dark:` variants for link backgrounds, borders, and text.
|
||||
- [x] 3.2 Update `src/components/home-dashboard.tsx` with `dark:` variants for all section cards, stat tiles, insight blocks, progress bars, and empty states.
|
||||
- [x] 3.3 Update `src/components/expense-workspace.tsx` with `dark:` variants for the form card, list card, inputs, and expense articles.
|
||||
- [x] 3.4 Update `src/components/paycheck-workspace.tsx` with `dark:` variants for the schedule panel, form, and paycheck list.
|
||||
- [x] 3.5 Update `src/components/recurring-expense-manager.tsx` with `dark:` variants for the panel, inline form, and definition articles.
|
||||
|
||||
## 4. Page header text
|
||||
|
||||
- [x] 4.1 Update hardcoded text colours in `src/app/add-expense/page.tsx` and `src/app/income/page.tsx` with `dark:` overrides.
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- [x] 5.1 Visually verify light and dark modes across the dashboard, add-expense, and income pages in the browser.
|
||||
- [x] 5.2 Verify that theme preference persists across page refreshes and that there is no flash of the wrong theme on load.
|
||||
Reference in New Issue
Block a user