Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
version: "10.33.0"
- uses: actions/setup-node@v4
with:
node-version: 24
node-version: 24.20.0
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
Expand All @@ -33,7 +33,7 @@ jobs:
version: "10.33.0"
- uses: actions/setup-node@v4
with:
node-version: 24
node-version: 24.20.0
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
Expand All @@ -47,7 +47,7 @@ jobs:
version: "10.33.0"
- uses: actions/setup-node@v4
with:
node-version: 24
node-version: 24.20.0
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
Expand All @@ -61,7 +61,7 @@ jobs:
version: "10.33.0"
- uses: actions/setup-node@v4
with:
node-version: 24
node-version: 24.20.0
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.20.0
127 changes: 117 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,26 @@ home server (unRAID) with no cloud dependency — one SQLite file holds all
data. See [README.md](README.md) for product-level detail, backup
procedures, and the list of permanent non-goals / deferred work.

## Plans

`foundational/PLAN_01_OVERVIEW.md` is the master doc for the ongoing UI/UX
overhaul, and each phase has its own `foundational/PLAN_01_PHASE_NN_*.md`
with tasks and acceptance criteria. Design lives in
[foundational/DESIGN.md](foundational/DESIGN.md) — the "Polymer" concept,
colour/type tokens, layout rules, copy rules and a decisions log. When a
phase is finished, tick its acceptance criteria and note any deviations in
that phase's own doc. A future `PLAN_NN_OVERVIEW.md` + `PLAN_NN_PHASE_NN_*.md`
pair follows the same pattern for the next multi-phase effort.

## Courtesy

The end user checks all changes after being made and before committing,
always ask before committing and pushing. This applies to every commit and
every push, not just the first one in a session — do not chain follow-up
commits/pushes onto an earlier approval without asking again.
The local dev server should always be left running after work is completed
and if it is required to be turned off for a change, it should be returned
to running again at the end of the session.
The end user checks all changes after being made and before committing —
ask before the first commit and push that opens a PR for a new piece of
work. Once a PR is open, keep iterating on it (fixing CI failures,
addressing review/Kilo feedback) without asking again for each commit and
push in that cycle; the owner will say up front if they want to hold off
on a round instead. Still ask again before merging, and before starting a
new, unrelated piece of work.
Clean up after yourself, do not leave any other sessions active.

## Monorepo layout
Expand Down Expand Up @@ -185,8 +196,25 @@ the `Joint Account Workbook.xlsx`, whose "Users" sheet - `Name`, `Email`,
runs in the browser after mount.
- Tailwind CSS v4 via `@tailwindcss/vite` (no separate `tailwind.config.js`
— config lives in CSS via the Vite plugin).
- UI/CSS conventions: see STYLEGUIDE.md for current button/form/checkbox/card
patterns — check it before introducing new UI patterns.
- **UI/CSS conventions**: [STYLEGUIDE.md](STYLEGUIDE.md) documents the
current, pre-overhaul button/form/checkbox/card patterns (being migrated
away from — see Plans above) and [foundational/DESIGN.md](foundational/DESIGN.md)
is the target design system ("Polymer") the overhaul is moving the app
onto — check whichever a given page has already moved to before adding a
new UI pattern.
- **Load the `/frontend-design` skill before any UI work** (design/copy/
layout decisions, not a pure logic or API change) and follow DESIGN.md.
Avoid the templated tells it calls out: all-caps eyebrows, middle-dot
meta strings (use badges instead), `→` suffixes on links/buttons,
identical card grids, gradient washes, and a single radius used
everywhere. Load the `dataviz` skill before any chart work.
- **shadcn-svelte** supplies the UI primitives, vendored into
`src/lib/components/ui/**` by its own CLI
(`pnpm dlx shadcn-svelte@latest add <name> -y`, config in
`apps/web/components.json`). Wrap and compose them in app-level
components; don't hand-edit anything under `ui/**` beyond what the CLI
generates — it's excluded from both lint and coverage for that reason
(see `eslint.config.js` and `vitest.config.ts`).
- Favour building reusable, unit testable components over large in-line
pages with sprawling sections, this will make re-use trivial.
- **Reuse and DRY are a priority, not an afterthought** — `routes/monthly`
Expand Down Expand Up @@ -230,6 +258,85 @@ the `Joint Account Workbook.xlsx`, whose "Users" sheet - `Name`, `Email`,
is in scope for that change even if it wasn't the original ask — leave
the file more DRY than you found it rather than adding a third copy.

## Verifying UI changes in a browser

Unit tests can't catch visual regressions. For any UI change, run the app
and **look at screenshots**: 390px and 1440px, light and dark. Log in with
the root `.env` `SEED_BRIAN_EMAIL`/`SEED_BRIAN_PASSWORD` (or
`SEED_ARIEL_*`) credentials against the dev server — see the Auth bullet
above. The login endpoint is throttled (5 attempts per 15 minutes per
IP+email, `apps/api/start/limiter.ts`), so reuse one Playwright session
(`page.setViewportSize`, dark mode via
`localStorage.setItem('theme','dark')` plus `.reload()`) rather than
logging in again for every viewport/theme combination:

```sh
node -e "
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ args: ['--lang=en-AU'] });
const page = await browser.newPage({ locale: 'en-AU', colorScheme: 'dark', viewport: { width: 390, height: 844 } });
await page.goto('http://localhost:5173/login');
await page.getByLabel('Email').fill(process.env.SEED_BRIAN_EMAIL);
await page.getByLabel('Password').fill(process.env.SEED_BRIAN_PASSWORD);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('http://localhost:5173/');
await page.goto('http://localhost:5173/<route>');
await page.screenshot({ path: '/tmp/check.png', fullPage: true });
await browser.close();
})();
"
```

Native date/time inputs follow the **browser's** UI language, not the
page's `lang`. Use `en-AU` (day-first, matching this app's AUD/AU-dates
audience) through both `locale` and `--lang`, so a `MM/DD` rendering isn't
mistaken for an app bug. The Playwright suite under `e2e/` runs against its
own isolated API/web ports (3334/5174) with fictional seed data and is
unaffected by the dev server's login throttle — prefer adding/extending an
e2e spec (see `e2e/action-menu.spec.ts` for a real-browser regression test)
over repeated manual dev-server logins when a check can be automated.

## Checks and reviews (multi-phase plans)

For a plan broken into phases (see Plans above), each phase is its own
branch and PR, and the next phase doesn't start until the current one has
been merged:

1. **Branch**: one branch per phase, named after its phase doc (e.g.
`phase-03-unified-outgoings`). Never work on `main`.
2. **Local gate**: `pnpm verify` (lint + typecheck + test) and
`pnpm test:e2e` both pass before anything is committed. For a
UI-touching phase, take the screenshots described above and review them
against DESIGN.md.
3. **Ask the owner before committing, and ask again before pushing and
opening the PR** (`gh pr create`) — see Courtesy above; this applies
once per phase, at the start of that phase's PR. Fix-up commits within
that same PR (CI failures, review feedback — steps 5–7 below) don't
need to re-ask each time.
4. **Wait for all PR checks to finish**: `.github/workflows/ci.yml`'s four
jobs (lint, typecheck, test, e2e) plus **Kilo Code Review**. Don't treat
"pending" as done.
5. **A green Kilo check is not proof there's nothing to fix.** Kilo's
check can pass while its review still has critical, unaddressed
comments. Once the checks are green, fetch and read every review
comment: `gh api repos/<owner>/<repo>/pulls/<n>/comments`, and also
`gh pr view <n> --comments` for top-level review bodies.
6. **Handle each comment on its own diff line**, whether it's from Kilo or
the owner. Fix it, or decide deliberately not to. Then reply on that
specific thread saying what changed, or why nothing changed
(`gh api repos/<owner>/<repo>/pulls/<n>/comments/<id>/replies`).
7. **Resolve each thread individually** after replying, via
`gh api graphql` and the `resolveReviewThread` mutation. Never post one
consolidated "here's everything I fixed" reply. A fix that adds commits
goes back through step 2's local gate, then commits and pushes directly
(see Courtesy above — no need to re-ask), and any new comments from the
re-run review get the same treatment.
8. **Close out**: tick the phase doc's acceptance criteria, note any
deviations, and update DESIGN.md's decisions log if the phase changed a
design decision. Merge only with the owner's explicit confirmation.
Start the next phase from an updated `main`.

## Testing

- **API**: Japa. Unit tests (`tests/unit/`) cover import parsers, services,
Expand Down Expand Up @@ -275,7 +382,7 @@ the `Joint Account Workbook.xlsx`, whose "Users" sheet - `Name`, `Email`,
Playwright is also available as a general local dependency for any
agent that wants to visually validate a web change in a real browser
beyond what Vitest/jsdom can check — e.g. `npx playwright test
--headed`, or drive one-off pages with `npx playwright screenshot` /
--headed`, or drive one-off pages with `npx playwright screenshot` /
ad-hoc scripts using `@playwright/test`'s `chromium.launch()`, rather
than only trusting a jsdom-based unit test for a visual/interaction
change.
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1

# ---- stage 1: build the SvelteKit static SPA ----
FROM node:24-alpine AS web-build
FROM node:24.20.0-alpine AS web-build
WORKDIR /app
RUN npm install -g pnpm
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
Expand All @@ -11,7 +11,7 @@ COPY apps/web apps/web
RUN pnpm --filter web build

# ---- stage 2: build the AdonisJS app ----
FROM node:24-alpine AS api-build
FROM node:24.20.0-alpine AS api-build
WORKDIR /app
RUN npm install -g pnpm
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
Expand All @@ -24,7 +24,7 @@ WORKDIR /app/apps/api/build
RUN npm install --omit=dev --no-audit --no-fund

# ---- stage 3: runtime ----
FROM node:24-alpine AS runner
FROM node:24.20.0-alpine AS runner
WORKDIR /app
RUN apk add --no-cache shadow su-exec
ENV NODE_ENV=production
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"license": "MIT",
"engines": {
"node": ">=24"
"node": ">=24.20.0 <25"
},
"exports": {
"./data": "./.adonisjs/client/data.d.ts"
Expand Down
20 changes: 20 additions & 0 deletions apps/web/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/routes/layout.css",
"baseColor": "neutral"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://shadcn-svelte.com/registry",
"style": "nova",
"iconLibrary": "lucide",
"menuColor": "default",
"menuAccent": "subtle"
}
9 changes: 8 additions & 1 deletion apps/web/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export default tseslint.config(
},
},
{
ignores: ['build/', '.svelte-kit/', 'dist/', 'coverage/'],
// src/lib/components/ui/** is vendored shadcn-svelte, generated by its
// CLI and not hand-edited (see foundational/DESIGN.md → Components) -
// same reasoning as its vitest.config.ts coverage exclusion. Several of
// its generated files trigger svelte/valid-compile's
// custom_element_props_identifier rule (a spread-props pattern the
// generator itself uses), which isn't something to "fix" in code we
// don't hand-maintain.
ignores: ['build/', '.svelte-kit/', 'dist/', 'coverage/', 'src/lib/components/ui/'],
}
)
10 changes: 9 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@internationalized/date": "^3.12.4",
"@lucide/svelte": "^1.45.0",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.63.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
Expand All @@ -26,13 +28,16 @@
"@testing-library/svelte": "^5.4.2",
"@testing-library/user-event": "^14.6.1",
"@vitest/coverage-v8": "^4.1.10",
"bits-ui": "^2.19.2",
"eslint": "^9.19.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.1",
"jsdom": "^29.1.1",
"prettier-plugin-tailwindcss": "^0.8.0",
"shadcn-svelte": "^1.6.1",
"svelte": "^5.56.1",
"svelte-check": "^4.6.0",
"tailwind-variants": "^3.3.1",
"tailwindcss": "^4.3.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.21.0",
Expand All @@ -41,6 +46,9 @@
},
"dependencies": {
"@mdi/js": "^7.4.47",
"svelte-dnd-action": "^0.9.77"
"clsx": "^2.1.1",
"svelte-dnd-action": "^0.9.77",
"tailwind-merge": "^3.7.0",
"tw-animate-css": "^1.4.0"
}
}
Loading
Loading