diff --git a/.claude/commands/newcomer-tour.md b/.claude/commands/newcomer-tour.md index 7428f7740..43d5feeda 100644 --- a/.claude/commands/newcomer-tour.md +++ b/.claude/commands/newcomer-tour.md @@ -1,7 +1,7 @@ --- # SPDX-License-Identifier: LicenseRef-OpenLBM-Docs-1.0 # SPDX-FileCopyrightText: 2026 FutureBuild, Inc. and OpenLBM contributors -description: Get oriented in the Gable codebase — the layout, the 41 backend modules, the connector seam, and the Lit frontend. +description: Get oriented in the Gable codebase. Covers the layout, the 47 backend modules, the connector seam, and the Lit frontend. argument-hint: [optional: a module, screen, or area you care about] --- diff --git a/.claude/commands/write-a-test.md b/.claude/commands/write-a-test.md index a5ceef1d5..b3c06afed 100644 --- a/.claude/commands/write-a-test.md +++ b/.claude/commands/write-a-test.md @@ -9,4 +9,4 @@ Use the **add-a-test** skill. Target: $ARGUMENTS -If no target was given, compute the untested-module list first and recommend one by value (invoice tax/rounding, account.PostTransaction, the ERP/portal money boundary, or frontend formatCents). Read the source before writing. Prove the test can fail. House rules apply: no tautological tests, characterization tests are labelled, a discovered bug gets an issue rather than a `want:` that encodes it. +If no target was given, compute the untested-module list first and recommend one by value (invoice tax/rounding, the account package's posting methods, the ERP/portal money boundary, or frontend formatCents). Read the source before writing. Prove the test can fail. House rules apply: no tautological tests, characterization tests are labelled, a discovered bug gets an issue rather than a `want:` that encodes it. diff --git a/.claude/skills/add-a-test/SKILL.md b/.claude/skills/add-a-test/SKILL.md index 84c510692..8720f24c2 100644 --- a/.claude/skills/add-a-test/SKILL.md +++ b/.claude/skills/add-a-test/SKILL.md @@ -40,8 +40,10 @@ already covered it: 1. **`invoice`** — tax resolution and rounding. `invoice.CreateInvoice` resolves the rate from the branch (`locations.default_tax_rate`) and falls back to `invoice.DefaultTaxRate = 0.0825`. Both paths, plus rounding, are worth pinning down. -2. **`account`** — `PostTransaction` is the *single writer* to the AR subledger and - `customers.balance_due`. It should be the best-tested function in the repo. +2. **`account`**: the package is the *single writer* to the AR subledger and + `customers.balance_due`, through its posting methods (`PostInvoice`, `PostCreditMemo`, + `RecordPayment`, `Apply`, `WriteOff`, and the voids, refunds and reversals). They should be + among the best-tested code in the repo. 3. **`customer`** — the credit-limit gate feeds order blocking. 4. **`quote`** — line totals, discounts, expiry. 5. **`portal`** — the dollars-vs-cents boundary with the ERP side (see the money table in @@ -63,7 +65,7 @@ Then study a module that already tests well — copy its idiom rather than inven ```bash sed -n '1,80p' core/internal/tax/service_test.go # mock repo + table-driven -ls core/internal/purchase_order/*_test.go # 6 test files, good examples +ls core/internal/purchase_order/*_test.go # 8 test files, good examples ls core/internal/pricing/*_test.go core/internal/gl/*_test.go ``` diff --git a/.claude/skills/check-my-contribution/SKILL.md b/.claude/skills/check-my-contribution/SKILL.md index 0de573517..cf1a0f623 100644 --- a/.claude/skills/check-my-contribution/SKILL.md +++ b/.claude/skills/check-my-contribution/SKILL.md @@ -33,9 +33,13 @@ make help | CI job | Gate? | What it runs | |---|---|---| | **Backend** | ✅ merge gate | `go vet ./...`, `go build ./...`, `go run ./cmd/migrate`, `go test -race -coverprofile=… ./...` | -| **Frontend** | ✅ merge gate | `npm ci`, `npx tsc --noEmit`, `npm run lint`, tests with coverage, `npm run build` | +| **Frontend** | ✅ merge gate | `npm ci` (from `web/`), `npm run typecheck`, `npm run lint`, tests with coverage, `npm run build` | +| **App Shape** | ✅ merge gate | `bash scripts/check-shape.sh`: required paths, manifest fields, CLAUDE.md import, JSON validity, no stray root artefacts | +| **Tauri shell** | ✅ merge gate | builds the front door, runs the Tauri unit tests, builds a Linux debug bundle, smoke-tests it under a virtual display | | **License (REUSE)** | ✅ merge gate | `reuse lint` (report) then `python3 .github/scripts/reuse_gate.py` (the gate) | -| **Docker Build** | ✅ merge gate | builds `core/Dockerfile` and `web/apps/desk/Dockerfile` | +| **Playwright (E2E)** | ✅ merge gate | full stack built (dev auth), Postgres migrated + seeded, `core serve` with `AUTH_MODE=dev`, nginx, then `npx playwright test` | +| **Local stack** | ✅ merge gate | `make up` (the same compose file a developer uses), then `make smoke` | +| **Docker Build** | ✅ merge gate | builds `core/Dockerfile` and `web/Dockerfile` (both bundles, one nginx) | | **Vulnerabilities** | ⚠️ **advisory only** | pinned `govulncheck` — `continue-on-error`, also runs nightly | `govulncheck` is deliberately **not** a merge gate: an upstream CVE landing overnight must not @@ -75,12 +79,12 @@ make test # cd core && go test -race ./... (needs Postgres) make test-short # cd core && go test -short ./... (no Postgres — DB tests self-skip) make cover # coverage profile + function summary -# Frontend -make fe-install # cd web/apps/desk && npm ci -make fe-typecheck # cd web/apps/desk && npx tsc --noEmit -make fe-lint # cd web/apps/desk && npm run lint -make fe-test # cd web/apps/desk && npm run test -- --run -make fe-build # cd web/apps/desk && npm run build +# Frontend (web/ is one npm workspace: apps/*, packages/*) +make fe-install # cd web && npm ci +make fe-typecheck # cd web && npm run typecheck +make fe-lint # cd web && npm run lint +make fe-test # cd web && npm run test +make fe-build # cd web && npm run build # Licensing make license-check # reuse lint (report) + .github/scripts/reuse_gate.py (the gate) @@ -130,8 +134,10 @@ Rotate the credential first, then rewrite the branch (`git reset` + recommit, or `git rebase -i`) before pushing. Also confirm you have not introduced `AUTH_MODE=dev` into any config that could reach a public -host. It is fine in `.do/app-demo.yaml` and `.do/app-staging.yaml` (demo data only); never -anywhere else. See [`SECURITY.md`](../../../SECURITY.md). +host. The current `.do/app-demo.yaml` and `.do/app-staging.yaml` both run with +`AUTH_MODE=production` (with placeholder `JWKS_URL`); leave them that way. `AUTH_MODE=dev` is +for local development only, never to be committed anywhere it could be picked up by a deploy. +See [`SECURITY.md`](../../../SECURITY.md). ## 4 · SPDX header must match the directory @@ -212,9 +218,9 @@ From [`AGENTS.md`](../../../AGENTS.md) § Pre-Flight Checks — verify each that additive and reversible; applies from empty. - [ ] **New endpoint**: under the correct prefix (`/api/v1`, `/api/portal/v1`, `/api/integration`, `/api/v1/a2a`) **and** wired into a `RegisterRoutes` call in - `core/cmd/server/main.go`. An endpoint that isn't wired in silently doesn't exist. -- [ ] **New public path**: if it must skip auth, it has to be in the whitelist in - `core/cmd/server/main.go` — and adding one deserves a security review. + `core/internal/app/serve/serve.go`. An endpoint that isn't wired in silently doesn't exist. +- [ ] **New public path**: if it must skip auth, it has to be in the `publicAPIPaths` whitelist + in `core/internal/app/serve/serve.go`, and adding one deserves a security review. - [ ] **Money on ERP pages**: rendered with `formatCents()` from `web/apps/desk/src/lib/utils.ts`, never `.toFixed(2)` on a cents field. Portal pages receive dollars and must not use it. - [ ] **UI**: design tokens from `web/apps/desk/tailwind.config.js`, no hardcoded colours; JetBrains diff --git a/.claude/skills/describe-a-workflow/SKILL.md b/.claude/skills/describe-a-workflow/SKILL.md index 94d8a801e..d144a65a0 100644 --- a/.claude/skills/describe-a-workflow/SKILL.md +++ b/.claude/skills/describe-a-workflow/SKILL.md @@ -25,7 +25,7 @@ Before the interview, spend a few minutes finding out what Gable does today, so "what's different about yours?" instead of "how does quoting work?". ```bash -ls core/internal/ # 41 domain modules — find the closest one +ls core/internal/ # 47 domain modules: find the closest one ls web/apps/desk/src/pages/ # the screens that exist today grep -rn "status" core/internal/order/model.go | head -30 ``` @@ -41,7 +41,8 @@ Useful orientation reads: Two known facts worth having in hand: - Orders today flow `DRAFT → CONFIRMED → FULFILLED`, plus `ON_HOLD` (a valid `orders.status` - as of migration 071). There is **no** pickup/will-call path and **no** `PICKED` step. + as of migration 071), `BACKORDERED` and `CANCELLED`. A will-call order is `delivery_type` `pickup`, fulfilled + with `picked_up_by` (ADR 0005 section 5.5); there is **no** `PICKED` step. - Fulfilling an order posts one tax-inclusive invoice plus a balanced `DR Accounts Receivable / CR Sales Revenue` GL entry and an AR subledger debit, in one transaction. Anything that creates or cancels a sale has to respect that. @@ -178,7 +179,7 @@ What number tells the owner this is working. - [ ] **Money/data integrity** — single writer, one transaction, no partial writes - [ ] **Migrations additive + reversible** — forward and down; idempotent - [ ] **No secrets committed** -- [ ] **Boot wiring** — registered via `RegisterRoutes` in `core/cmd/server/main.go` +- [ ] **Boot wiring**: registered via `RegisterRoutes` in `core/internal/app/serve/serve.go` - [ ] **Rollback plan** - [ ] **Observability** — what gets logged/measured; CI actually runs the new tests diff --git a/.claude/skills/explain-this-code/SKILL.md b/.claude/skills/explain-this-code/SKILL.md index 941c221fa..21e33d67e 100644 --- a/.claude/skills/explain-this-code/SKILL.md +++ b/.claude/skills/explain-this-code/SKILL.md @@ -2,7 +2,7 @@ # SPDX-License-Identifier: LicenseRef-OpenLBM-Docs-1.0 # SPDX-FileCopyrightText: 2026 FutureBuild, Inc. and OpenLBM contributors name: explain-this-code -description: Orient a newcomer in the Gable codebase — the modular-monolith layout, the 41 domain modules under core/internal, the core/pkg/apps connector seam, and the Lit web-component frontend — and teach them how to find things themselves. Use when the user says "where do I start", "how does this codebase work", "give me a tour", "I'm new here", "explain the architecture", "where does the order code live", "how does the frontend work", "what is core/pkg/apps", "how do I find the code for the delivery screen", "/newcomer-tour", or asks what any module or file does. +description: Orient a newcomer in the Gable codebase. Covers the modular-monolith layout, the 47 domain modules under core/internal, the core/pkg/apps connector seam, and the Lit web-component frontend. Teaches them how to find things themselves. Use when the user says "where do I start", "how does this codebase work", "give me a tour", "I'm new here", "explain the architecture", "where does the order code live", "how does the frontend work", "what is core/pkg/apps", "how do I find the code for the delivery screen", "/newcomer-tour", or asks what any module or file does. --- # explain-this-code — teach the map, not just the answer @@ -23,12 +23,12 @@ Give this once, at the start, tailored to what they care about. ``` gable/ - core/ Go 1.26 — one binary, ~41 domain modules (the "modular monolith") + core/ Go 1.26: one binary, 47 domain modules (the "modular monolith") cmd/core/ the one binary: `core serve` (wires every module repo→service→handler→routes), `core worker` (background jobs), `core migrate` (applies core/migrations/*.sql in order), `core seed` (the demo dataset) - internal/ the 41 modules. THIS is where business logic lives. + internal/ the 47 modules. THIS is where business logic lives. pkg/ shared plumbing (database, middleware, audit, httputil, metrics…) pkg/apps/ the connector seam — the stable surface third parties plug into migrations/ plain numbered SQL: 001_…, 002_… @@ -82,7 +82,7 @@ If a route isn't in `routes.ts`, it belongs to a **converted app** and is declar ### A backend behaviour ```bash -ls core/internal/ # the 41 modules +ls core/internal/ # the 47 domain modules and seven support directories grep -rn "func (s \*Service)" core/internal/order/service.go | head -20 ``` @@ -167,8 +167,9 @@ Conversion recipe and phases: [`docs/modularization-blueprint.md`](../../../docs `@property({ attribute: 'route-id' })`. - **State:** `@state()` for internal, `@property()` for external. Cross-component state lives in framework-agnostic singleton services under `web/apps/desk/src/services/`. -- **Never hardcode a colour.** Tokens are in `web/apps/desk/tailwind.config.js`. JetBrains Mono for all - numbers, SKUs, prices, and dimensions; Inter for body text. +- **Never hardcode a colour.** Tokens are in `web/apps/desk/tailwind.config.js` (theme defined + in `web/packages/design-system/tailwind/theme.js`). JetBrains Mono for all numbers, SKUs, + prices, and dimensions; Outfit for body text. - **Money:** `formatCents()` in `web/apps/desk/src/lib/utils.ts` for ERP surfaces. Portal surfaces get dollars from the API and format directly. Mixing them renders `$738.87` as `$73,887.00`. @@ -178,10 +179,10 @@ Conversion recipe and phases: [`docs/modularization-blueprint.md`](../../../docs - **Router is stdlib `net/http.ServeMux`** (Go 1.22+ pattern matching), not Chi or gorilla. - **Default Postgres port is 5434**, matching `docker-compose.yml` — not 5432. -- **Money conventions are not uniform.** ERP orders/invoices and the `account` module use - `int64` cents; portal, quotes, and DailyTill use `float64` dollars — on some of the same - columns. `AGENTS.md` § "Money convention is not uniform across modules" has the table. Read - it before touching anything financial. +- **Money conventions are not uniform.** ERP orders/invoices, the `account` module, quotes, + and customers wire fields as `int64` cents; portal and DailyTill use `float64` dollars on + some of the same columns. `AGENTS.md` § "Money convention is not uniform across modules" + has the table. Read it before touching anything financial. - **AR balance is derived, not read.** Compute from open invoices (`invoice.OpenInvoiceStatuses`), not from `customers.balance_due` — that column is a secondary subledger figure and can drift. diff --git a/.claude/skills/improve-docs/SKILL.md b/.claude/skills/improve-docs/SKILL.md index 24e8bb86e..7a1789fdb 100644 --- a/.claude/skills/improve-docs/SKILL.md +++ b/.claude/skills/improve-docs/SKILL.md @@ -96,10 +96,9 @@ These are real inconsistencies that a docs contributor can legitimately fix: - **Money conventions.** `AGENTS.md` says money-as-cents is *the target, not current reality* — ERP orders/invoices and `account` use `int64` cents while portal, quotes, and DailyTill use `float64` dollars. Any doc that states "money is cents" flatly is overstating it. -- **Branch target.** `CONTRIBUTING.md`, the PR template, and `README.md` all say contributions - target **`staging`**. `AGENTS.md`'s branch table still says "Community PRs target - [`community`]". That's stale — but `AGENTS.md` is a high-traffic file, so fix it in its own - small PR with the evidence, don't bundle it. +- **Branch target.** `CONTRIBUTING.md`, the PR template, `README.md` and `AGENTS.md` all say + contributions target **`staging`** (with `main` as the fast-forward target). Older notes + referred to `community`; that branch model is retired. - **Coverage claims.** Check them against reality rather than repeating them: `find web/apps/desk/src -name '*.test.ts' | wc -l` and, for the backend, `for d in core/internal/*/; do [ -z "$(ls $d*_test.go 2>/dev/null)" ] && echo "$d"; done`. diff --git a/.claude/skills/licensing-check/SKILL.md b/.claude/skills/licensing-check/SKILL.md index a82397c37..fb0f73bdc 100644 --- a/.claude/skills/licensing-check/SKILL.md +++ b/.claude/skills/licensing-check/SKILL.md @@ -170,9 +170,14 @@ specifications — that's an IP problem this project has explicitly cleaned up o ## 5 · If the mapping itself looks wrong If a file's header disagrees with `LICENSE-MAP.md`/`REUSE.toml`, or a directory isn't covered -at all, that's a real finding worth reporting. Note that `REUSE.toml` currently annotates -`core/**`, `web/apps/desk/**`, and `docs/**` — root-level markdown and `.claude/**` are **not** -covered by annotations, which is why files in those places carry their headers inline. +at all, that's a real finding worth reporting. Note that `REUSE.toml` annotates the major +source components (`core/internal/**`, `core/pkg/**` with the `core/pkg/apps/**` carve-out, +`core/cmd/**`, `core/migrations/**`, `web/apps/**`, `web/packages/**`, `tauri/**`, `docs/**`) +plus several root-level paths via globs (`*.md`, `NOTICE`, `.github/**`, `.do/**`, +`.claude/**`, `Makefile`, `scripts/**`, `manifest.yaml`, `docker-compose.yml`, +`core/go.mod`, `core/go.sum`, `core/Dockerfile`, `core/scripts/**`). The inline SPDX +headers you see in `.claude/skills/*/SKILL.md` and in `CLAUDE.md`/`AGENTS.md` are kept for +clarity even where an annotation would suffice; the REUSE gate reads both. Do **not** edit `REUSE.toml` or `LICENSE-MAP.md` to "fix" it yourself — those are maintainer- owned. Use **`report-an-issue`** and include the exact file, the header it has, and the header diff --git a/.claude/skills/report-an-issue/SKILL.md b/.claude/skills/report-an-issue/SKILL.md index 371caec95..431b80683 100644 --- a/.claude/skills/report-an-issue/SKILL.md +++ b/.claude/skills/report-an-issue/SKILL.md @@ -83,7 +83,7 @@ The surface trees are: | "the till", "the front counter", "POS" | `/pos` | `web/apps/desk/src/pages/pos/` | If the problem is in data rather than display, the backend module is under -`core/internal//` — there are 41 of them. `ls core/internal/` and pick the +`core/internal//`; there are 47 domain modules. `ls core/internal/` and pick the obvious one (`order`, `invoice`, `delivery`, `inventory`, `pricing`, `payment`, `pos`…). Record the **directory**, not just a guess: the issue template asks for it, and Gable is