Release notes
Changelog
Every release in the open — what changed, and how it was verified before it shipped.
0.3.36
Latest
39
Releases
271
Changes
Latest v0.3.36
Added 2
- Read-only agent API: GET /api/v1/agent/manifest and GET /api/v1/agent/context require a member session. The context contains only aggregate occupancy/Form K counts, verified Operating/CRF balances and up to 50 deadlines. It omits contacts, payment references and transaction descriptions, and uses private, no-store + Vary: Authorization. The manifest lists only the dedicated context route and clarifies it does not grant access to other APIs.
- Safe ledger retry keys: Idempotency-Key is validated, bound to a request fingerprint, safe for concurrent identical retries, and rejects changed-body reuse with 409. Cache capacity is bounded; successful results are not evicted early, new keys receive 503 under full capacity, and the single-process / 24-hour scope is documented.
Changed 2
- Fastify rejects unknown schema fields, limits request bodies to 256 KiB and requests to 30 seconds; auth identity fields and Rosa's question/facts now have explicit shape and length limits.
- Backend API version is independently bumped to 0.3.27; site version is 0.3.36.
Fixed 4
- Form B/F and CRT HTML outputs escape council and request data and ship no-store, nosniff, and restrictive CSP headers.
- JSON agent context preserves council names as text; HTML escaping occurs only at HTML rendering boundaries. Form B/F HTML titles, unit IDs, statuses and disclosures are escaped, and responses use restrictive security/cache headers.
Verified 1
- Backend typecheck clean; full backend suite: 301 passed, 6 skipped. Frontend suite: 309 passed; Svelte check: 0 errors / 0 warnings; production build green; i18n audit 1065 keys × 9 locales and contrast audit 218 pairs pass.
- v0.3.35Added
- PUBLIC_API_BASE_URL=/ — one host serving both the site and the API. The value / (or the word same-origin) now means *the origin this page was served from*, so a single-host deployment needs no absolute address written down, keeps working when the host's address changes, and involves no CORS at all — a same-origin request never asks.
- A wiring table in SOURCE-OF-TRUTH.md and three documented shapes in .env.example / README.md / docs/DEPLOYMENT.md (one host · tailnet · public HTTPS behind a tunnel), each naming the two variables that have to agree.
Changed- resolveApiBase() takes the origin as a third input and returns demo mode when there is none (SSR/prerender). The origin is only knowable once the page is running, so before hydration the widgets render demo data and then settle on live — the same shape the runtime override already had.
Fixed- A base value that is not a real origin is now ignored instead of used. fetch('//evil.example/api/v1/units') is a protocol-relative redirect to somebody else, so a mistyped — or hostile — PUBLIC_API_BASE_URL would have handed a council's bearer token to that host. A bare path (/api) and a javascript: URL are ignored the same way, and the site falls back to demo mode rather than pointing somewhere untrusted. A URL with a path (https://host/openstrata) is still accepted, for a reverse proxy in front of the API.
Verified- Chrome, built artifact, no runtime override of any kind (every openstrata-* key cleared): the site and the API were served from one origin (static build at the root, /api proxied to a memory-backed host) and the signed bill link rendered $921.00, LMS2847-305-202610 and 305 · Evergreen House.
- The bundle for PUBLIC_API_BASE_URL=/ inlines the same-origin rule; the CSP keeps its documented fallback (deriveOrigin('/') is not an origin, and connect-src 'self' already covers a same-origin call); static/_headers is unchanged in git.
- v0.3.34Added
- Cross-origin access, opt-in and exact. The site is a static Cloudflare build and the API is the council's own host, so a browser blocked every /api/v1/* call before it left the tab — both documented exposure paths said live data works, and neither could from a browser. backend/src/api/cors.ts holds the rules as a pure module and CORS_ORIGINS turns them on.
- CORS_ORIGINS (comma-separated) documents both shapes in .env.example, backend/API.md, docs/DEPLOYMENT.md and backend/README.md, with the tailnet example spelled out end to end.
Changed- A preflight is answered 204 in an onRequest hook, before any route runs, and the hook also runs on error responses — so a 401 from an allowed origin stays readable in the browser instead of arriving as an opaque CORS failure. The route still wants a body; the browser only asked whether it may send one.
- Vary: Origin rides on every response that carried an Origin, allowed or not, and merges with whatever the route already set.
Fixed- A promise the docs made and the code did not keep. docs/DEPLOYMENT.md described Option A (tailnet-only) as *“live data works for browsers on the tailnet”* and Option B as *“add the site origin to the backend's CORS allowlist”* — there was no allowlist to add to. Both now name CORS_ORIGINS in their commands.
Verified- On the built server, with logger: false: no CORS header at all when CORS_ORIGINS is unset (a same-origin deployment is untouched); a preflight from an allowed origin answers 204 with the origin echoed, POST in the methods, and authorization in the allowed headers; a second origin (http://…ts.net:8080) is echoed for its own request; a lookalike host, a wrong scheme and a wrong port each get no permission header; an unauthenticated call from an allowed origin still returns 401 with the permission header; the real flow runs end to end cross-origin (register → authed /units); and CORS_ORIGINS=* grants nothing (Access-Control-Allow-Origin is absent, never *).
- npm test in backend/ — 285 tests (17 new) · tsc clean · frontend check 0/0 · 303 frontend tests · audit:i18n 1065 × 9 · build green.
Known issues- A council host is still not deployed, so no build has been pointed at a real one. The browser-level proof here is against a local host on the same machine; the tailnet and tunnel shapes need a running host and are recorded as open questions for Kimi in docs/KIMI-HANDOFF.md.
- v0.3.33Fixed
- PUBLIC_API_BASE_URL now actually reaches the browser. The variable was documented as the build-time way to point a static build at a council host, but src/lib/api/config.ts read it through import.meta.env, which Vite populates only for its own VITE_ prefix. The production bundle compiled to const i = {}, so the value was always undefined and only the runtime localStorage['openstrata-api-base'] override ever worked. Fixed by declaring envPrefix: ['PUBLIC_', 'VITE_'] in vite.config.ts — the documented PUBLIC_ namespace, with VITE_PUBLIC_SENTRY_DSN still exposed.
- The prebuild CSP had the other half of the same bug. scripts/generate-csp.mjs read process.env only, so an operator who followed the documented workflow and put the base in .env got a bundle pointed at their host but a connect-src that still allowed only https: — which the browser then blocks for a plain-http tailnet host. It now loads .env, .env.local, .env.production and .env.production.local in Vite’s precedence order (util.parseEnv), with an explicit shell variable still winning.
Changed- The precedence rule is a value, not a side effect. apiBaseUrl() now delegates to a pure resolveApiBase(stored, built): a stored override wins, an empty or absent value is demo mode, whitespace and trailing slashes are trimmed. Behaviour is unchanged; the rule is now testable without a browser.
Verified- The build-time base, end to end, with no runtime override of any kind: PUBLIC_API_BASE_URL=http://127.0.0.1:4173 npm run build, then in Chrome every openstrata-* key cleared and localStorage['openstrata-api-base'] null — the signed bill link rendered $921.00, LMS2847-305-202610 and 305 · Evergreen House from the host. The same page on the previous build showed the *“this site is not connected to a council host”* card.
- The unset path still degrades safely: a build without the variable contains no baked origin (grep finds none in _app/immutable), compiles the read back to the undefined fallback, and serves the demo sheet as before.
- v0.3.32Added
- A signed bill link, so an owner can pay without a council login. A treasurer prints one link per bill (POST /api/v1/payments/link, treasurer+) and sends it; the owner opens /pay?unit=305&rail=etransfer&t=… on their phone and pays from their own bank. The link is the credential: ospl1.<payload>.<sig>, HMAC-SHA256 under a key derived from PAY_LINK_SECRET (or AUTH_SECRET when unset) with the label openstrata/pay-link/v1. It states one lot, one amount, one reference and one payee, until it expires — 30 days by default, clamped to 1..120, and a link that has expired is refused rather than re-signed.
- backend/src/rails/pay-link.ts — signing, verification and the bill a payer sees, with every reason a link can be unusable named (malformed, bad signature, expired, not yet valid, unknown version, incomplete claims). A link can never verify as a JWT and a JWT never as a link.
Changed- The link survives the sheet. sheetSearch() carries the token through a rail or lot change, so a URL rewrite mid-payment cannot silently end it, and providerReturnHref() strips it from the address handed to the bank: the credential is remembered per request id in sessionStorage, not put in Interac's return URL.
- PAY_LINK_SECRET / PAY_LINK_TTL_DAYS documented in backend/.env.example, and the three new routes in backend/API.md.
Fixed- A refusal that looked like success. A host with no Interac provider answered 200 { ok: false }; the payer's page read that as a started request and showed a *Live request* badge on nothing. The route now answers 503, and startInteracRequest / interacRequestStatus refuse any body whose ok is not true before the dialog touches it.
- isTrustedGateway(undefined) threw instead of refusing — a host that answers without a gateway URL must send nobody away, and now does not crash.
Verified- Chrome, built artifact, memory-backed host on the same origin, no session on the page (localStorage['openstrata-token'] = null): the signed link renders $921.00 / LMS2847-305-202610 / fees@lms2847.invalid with the lot fixed and the receipt table gone; starting the request answers viaLink: true, amountBasis: 92100, request SIM-LMS2847-305-202610-0001, while the body it sent claimed amountBasis: 1 and unitRef: U-999 and was ignored; the payer reads that request back with the link (200), another bill's link gets 404, and no credential gets 401. A tampered token shows *"This bill link cannot be used"* with reason bad signature and no amount; switching to Bitcoin keeps the token in the URL and the council's amount on screen; a plain /pay visit is still the demo sheet.
- check 0/0 · 293 frontend tests · audit:i18n 1064 keys × 9 locales · 267 backend tests (17 new for the link, plus the refusal and loopback cases) · build green.
Known issues- The public demo still cannot start a real request: with no Interac merchant credential the host answers from the labelled rehearsal provider, so the payer sees *Simulation — nothing was sent*. The link path is live end to end up to that provider boundary.
- PUBLIC_API_BASE_URL is not applied at build time. src/lib/api/config.ts reads import.meta.env.PUBLIC_API_BASE_URL, which the production bundle compiles to an empty object, so only the runtime override (localStorage['openstrata-api-base']) actually points a build at a host. Found while verifying this release; fixed in v0.3.33 (envPrefix in vite.config.ts, plus .env loading in the prebuild CSP script).
- v0.3.31Added
- Interac e-Transfer as a real rail on the pay sheet. The e-transfer panel (still on /pay?unit=305, as asked) carries one button above the copy fallback: Open your bank and pay. It starts a Business Request Money request with the lot's amount and the sheet's reference already in it, hands the payer to Interac to choose *their own* financial institution and approve from their own banking app, and then takes the answer back with the request id in the URL.
- src/lib/interac.ts (+ interac.test.ts, 26 tests) — the rules, out of the component so they are testable without a browser: the provider status vocabulary (seven states; anything unrecognised is refused, never guessed), the institution list, the request builder (integer cents, one normalised shape per payer contact), the gateway allow-list, and the return contract.
Changed- Honesty, in two places a payment page could lie. A status that arrives in a link is a claim: with no council host to confirm it, the card reads *"Returned from your bank — waiting for the council to confirm"* and never shows "paid" on a URL alone. And a request never moves backwards — a replayed or out-of-order report cannot un-deposit a completed transfer or resurrect a stopped one.
- A configured host that is unreachable is a failure, not a rehearsal. Demo mode (no host at all) runs the labelled simulation; a host that is configured and does not answer says *"Your council's host could not be reached, so no request was started."* The rehearsal is never used to paper over a broken host.
Fixed- The card moved to the answer the moment the payer acted, and one build did not. After a simulated approval nothing further happened on screen while the bank picker stayed up — a lie of omission found in the browser sweep, not by reasoning. The card now moves to the answer on approval and the deposit beat updates it in place.
- The dialog no longer prints "approved and deposited" on a link's word. The first version showed the URL's status in full; the headline is now the claim's own wording until a host confirms.
Verified- Chrome, on the built artifact: all 40 lots (the dialog's amount must equal the lot's own amount — 0 mismatches), the three zero-balance lots (no button at all — nothing owed, nothing to send), the decline and cancel paths (each lands on its own state and its own URL), four return links (deposited, approved, declined, an unknown status), Escape, keyboard entry (focus + Enter opens it), and 0 text below 4.5:1 inside the dialog at 1280px and 390px in both themes with no page overflow.
- The live branch against a local stand-in host (self-signed HTTPS, the real request shape): a trusted gateway shows Continue to Interac with no bank list (that list is Interac's page, not ours); an untrusted gateway is refused with nothing opened; an unreachable host produces the failure line, not a rehearsal.
Known issues- Nothing payable: every destination and QR payload on /pay is still a demo string, and the rehearsal provider is what answers on the public demo.
- A live request needs a council host and a signed-in owner (the API routes are council-scoped like every other /api/v1 route), so on the public demo the button always rehearses.
- v0.3.30Added
- The one-lot pay sheet (/pay) — the second of the four queued UI upgrades, picked by Cam. The page was a rail list plus a pay dock; it is one sheet now. One amount in the largest type on the page, its parts (fee, arrears, late fee, fine, levy) under a dashed rule and only the parts actually owed; three large choices as real input[type=radio]s under styled labels in a fieldset with a screen-reader legend, so keyboard, arrow keys and announcement stay the browser's; and the picked rail in full — destination, exact BTC and sats, the QR, and the steps. A lot picker and deep links (/pay?unit=108&rail=bitcoin) let a treasurer send a lot the exact rail they agreed on.
- PAD and cheque are tucked under the sheet in a native <details>: they are not what a treasurer sends from, and they must stay reachable.
Changed- The rate lock is a stamp, not a control. It states how long the fiat amounts are good for and nothing else. When it expires the sheet says so in words *and* moves the BTC/sats row to the warning ink, because a stale rate that still looks live is the one dishonest thing this page could do. Both the note and the stamp's own icon re-price it.
- The sheet has exactly one writer per value. The chosen rail is owned by the component; ?rail= seeds it once on arrival and is an output after that.
Fixed- A rail/URL race that could show the wrong payment destination. Reseeding the chosen rail from ?rail= in an $effect looked harmless. A traced run showed the failure: the click wrote the rail and began the URL write, then the effect re-ran against the URL *as it still was* and put the old rail back, so the tick and the URL said e-Transfer while the panel, the rows and the QR showed Lightning's invoice. replaceState navigations also did not always deliver a page-store update, so making the URL the single source of truth would have stuck the same way. One writer per value; the rules (sheetRail() falls back to the first choice for a link naming pad, cheque or nothing) live in src/lib/pay-sheet.ts with 13 tests.
- The 15-minute clock can never print a negative remainder (lockClock clamps at 00:00), and a hostile time reads as expired rather than as a live rate.
Verified- Chrome on the built artefact: every choice in sequence, rapid-click sequences (three and four picks with ~30ms between), six deep links, all 40 lots (the shown amount must equal the option's own amount, with the URL intact), the zero-balance lots, the full expiry → re-price → re-expire flow (scratch build, LOCK_SECONDS = 3), and 0 text below 4.5:1 at 1280px and 390px in both themes with no page overflow. 261 frontend tests, 1017 i18n keys x 9 locales, audit:contrast pass (218 pairs + 4 guards).
- v0.3.29Changed
- The dashboard chrome owns its ramp. The sidebar is navy in *both* themes, but its ink was still resolving to the shell's --muted / --faint / --orange-text, which flip at night. Read in light mode those are a dark slate (2.2:1 on navy) and a dark rust (2.2:1). .sidebar now re-points six shell tokens at a fixed --chrome-* ramp for its whole subtree, so a rule — or a component's own scoped style — inside the chrome cannot reach a flipping step by accident. Same treatment for the auth modal's navy brand panel (slab ramp), and the inverse surfaces now re-point the accent ink too, so a focus ring on navy stays legible.
- Card surfaces flip by construction. Nine fills in src/app.css were pinned #fff and re-declared in a separate .dark block; they use var(--paper) now and four redundant overrides are gone with them.
Fixed- var(--surface-3, …) and var(--shadow-sm, …) referenced tokens that do not exist. CSS does not warn: the declaration is dropped and the FALLBACK wins, so the auth modal painted a fixed near-white plate (light ink on it at night) and the start card used a stale shadow literal.
- The auth modal's password-strength verdict, step labels, step numbers and field labels were literal greys at 2.44:1–3.6:1; the strength verdict now rides the same three status tokens as every other verdict in the app.
Verified- Chrome, 28 routes × light and dark (56 loads, 10,976 text nodes): zero below 4.5:1. Plus the ⌘K search modal, the topbar menus and the auth modal in both themes at zero. The harness asserts the newest token resolved on every load, so a service-worker-cached sheet cannot be scored as a fresh build.
- The audit grew 194 → 218 token pairs and gained four structural guards (chrome scope, undefined var(), literal-white fills in the stylesheet outside print, and the existing fill-literal guards). Each new guard was reintroduced against its own fix and confirmed to exit 1.
- v0.3.28Fixed
- Month nav buttons in the deadline calendar use their real aria-labels (calPrevMonth / calNextMonth, x9 locales) instead of borrowing the task-list strings; the month-title button lost a dead ternary.
- v0.3.27Added
- Deadline popover. Clicking a calendar day opens a small panel pinned under that cell: the day's deadlines in full, each with a one-tap link to the tools panel where the item is handled. The day cells became real buttons — keyboard reachable, aria-expanded on the open day, hover affordance gated behind @media (hover: hover). The popover clamps inside the grid on narrow phones and closes on ✕, Escape, outside click, or month navigation.
- v0.3.26Added
- E2E money-path suite. The real Fastify server boots over HTTP and walks the full council loop: register → units → billing run (late notice lands on the arrears unit) → Lightning quote with the per-site receive label → confirm posts to the unit's AR ledger → reconcile → hash-chain verification → deadline calendar → Form B issued while Form F is withheld on the debtor unit → another council sees nothing. 10 tests. CI now gates the *sequence* a real council runs, not just the parts.
- Guided first-month walkthrough. Five steps in treasurer order (bill → collect → reconcile → review → close) mounted at the top of the tools page, each linking to the real interactive panel where that step happens. Device-local progress (src/lib/first-month.ts, 6 tests) with reset, hide and restore — a nudge with a reset button, never a gate.
Fixed- The building modal lied. "Reserve funds" computed health × 2400 — demo math wearing a suit. The modal now shows health, open actions and the building's real issue, and says plainly where live detail lives.
- Fake buttons. Every dashboard button that only fired a toast now navigates to the real destination: plan-meeting and log-request to the tools demos, legal source to /legal, activity rows and building ••• to Strata Tools. A button either does the thing or it goes.
- v0.3.25Added
- The tour is reachable again. The first-run popup with the intro video was never deleted — fresh-browser checks on production proved the card works — but it only ever showed on the "just looking" path, and once dismissed it was unrecoverable. The dashboard welcome row now carries a replay pill ("Watch the 60-second intro again", all 9 locales) that reopens the full card on demand. Tour.svelte itself needed no changes.
- Saved searches. A pin button in the ⌘K results footer writes to openstrata-saved-searches (device-local, capped at 8, deduped case-insensitively; src/lib/saved-searches.ts, 6 tests). The dashboard renders the pins as one-tap /search?q= chips under the task list, × to unpin; pinning in the modal updates the strip live via a window event.
- v0.3.24Added
- Recent searches in the ⌘K empty state. The modal now remembers the last five queries, device-local under openstrata-recent-searches (the same privacy posture as the wizard draft — no account, nothing leaves the browser). src/lib/recent-searches.ts is pure with 7 tests: queries cap at 80 characters, dedupe case-insensitively ("form b" == "Form B"), a new query moves up instead of duplicating, and one tap removes an entry. Corrupt storage degrades to an empty list.
- Scoping chips. Ten one-tap filters (Pages · Documents · Manual · Posts · FAQ · Templates · Legal · Sources · Tools · Tasks) sit above the results. Chips scope *before* ranking, so a query that missed the global top-12 cut still surfaces inside its group. Short chip labels come from searchGroupShort(t) — long names like "Print-ready documents" stay on the per-result eyebrows — and groupsInIndex() renders the chips from the index itself, so a future group cannot grow a chip that filters to nothing.
Changed- The last hard-coded copy went through the catalog. The thank-you page's flow prose (variant titles, the four step bodies) moved to src/lib/thankyou.ts as canonical English — the same rule as the manual and the custody page, because it is operational honesty about what just happened to someone's data — and its chrome (title, meta, buttons, "No hidden steps") went through the catalog in all 9 locales. Also catalogued: the home proof band ("0% custody, always." / "Statutory clocks that don't slip."), the design-page type specimens, the privacy/terms titles and metas, the wizard's "What happens next →", and the footer's Privacy/Terms links. The hard-coded-copy audit fell from 24 warnings to 0. Two keys (thanksNoHiddenSteps) end with an em dash instead of a colon — the audit's key regex reads a trailing :' as a phantom key, which surfaced as bogus parity errors.
- Help lands on the six-step guide. The dashboard sidebar's "Need a hand?" card and the HostConnect strip's help link now deep-link to /docs/manual/getting-started instead of the FAQ, and the hint copy says so (helpStartHere), because a person asking for help wants the walkthrough, not an index.
Guarded- Every indexed href must resolve to a canonical route (the sitemap's list, not the nav — pages like /custody deliberately live outside the bar). An index row pointing at a dead page now fails the suite.
- Short chip labels never collide within a locale, and the short/long label maps always cover the same ten groups.
- v0.3.23Fixed
- The ⌘K empty state names all ten groups, in every locale. The search modal's placeholder copy said “Search across pages, posts, FAQ, templates, and legal sources” — written when the index had five groups. v0.3.22 grew the index to ten and the copy lagged behind the product. The hint now names every group the index serves, in all nine locales, and a drift-guard test ties the canonical English hint to the group list out of buildSearchIndex: an eleventh group now fails the suite until the hint is rewritten.
Changed- One home for the group labels. The modal's inline group-label map moved into search.ts as searchGroupLabels(t), so the per-result eyebrows, the empty-state hint and the index itself are derived from the same source.
- Two labels were quietly English-only. primarySources (“Primary and official sources”) and strataTool (“Strata Tool”) had no non-English catalog entries, so eight locales rendered them as English fallbacks. All eight now carry them; “Strata Tool” stays as the product name in every locale, matching the file's own convention (openStrataToolsCta).
- v0.3.22Added
- “Continue where you left off” — the wizard keeps your draft. The 8-step Building Template Wizard held everything in component state: stop halfway — a phone call, lunch, a closed tab — and the whole building came back blank. It now persists a device-local draft (src/lib/resume.ts, localStorage, same convention as the setup ticks and the first-visit choice) on every step change, and the dashboard answers with one chip above the greeting: *Continue where you left off — Harbour House — 4 of 8 · in progress*. One click lands back on the exact step with every field as typed — name, address, unit count, banks, enabled sub-accounts, services, rails, bylaw choice — and the wizard confirms the restoration with a quiet “Draft restored” notice. Dismissing the chip discards the draft; generating the config clears it, because a finished build is not unfinished work. Parse degrades to “no draft” on corrupt or hostile storage, and a draft is only offered when it has a name and the visitor got past step 1. 8 tests.
- ⌘K reaches the whole site. Site search now indexes the four print-ready documents — Form B and Form F are reachable by name for the first time, deep-linked to /documents?doc=… — the eight manual sections, and the dashboard's “What needs doing” task list. The index goes from seven groups to ten, so nothing a council can name is more than one keystroke away.
Verified- npm run check 0/0 · 214 tests (was 196: +8 resume, +7 banner, +3 search) · audit:i18n 921 keys × 9 locales · audit:contrast 116 pairs · build green.
- v0.3.21Changed
- The header is four things instead of six. Compliance and Docs left the bar and joined the Library menu, beside the legal sources, the templates and the printable documents — which is what they are. A council reads compliance material; it does not “go to compliance” the way it goes to its own dashboard. Nothing became unreachable: both are one click away, still in the footer, still in site search.
- The first visit is a question, not a wall. A brand-new visitor used to land on a full dashboard — four metric cards, a building list, an activity feed, four panels — for a building that is not theirs, with numbers that are not real. They now get one plain question with three ways in: look around, set a building up, or sign in. The dashboard appears the moment they choose, and the choice is remembered on the device only. - Visibility is decided before the first paint by the same class-before-paint trick the theme switch already uses, so there is no flash — and because it is CSS rather than a conditional render, the prerendered markup still contains the whole dashboard for a crawler. - The first-run tour now waits its turn. One first-run experience at a time; answering the question opens the tour over the dashboard the visitor just asked to see.
Fixed- Breadcrumbs mislabelled /documents as Docs. /documents.startsWith (/docs) is true, and the trail took the *first* match — so the printable documents page carried a crumb that linked to the wrong page. Resolution now happens on a segment boundary and prefers the deepest href, which also keeps /docs/manual landing on Docs.
Verified- npm run check → 0 errors, 0 warnings. npm test → 196 passed (was 164). audit:i18n → 909 keys × 9 locales, parity green. audit:contrast → 116 pairs. Build green.
- Browser-verified on the production preview with the service worker unregistered: a first visit shows the question with the dashboard hidden and no tour overlay; choosing “just looking” reveals the dashboard in place, stores the choice, and opens the tour over it; the header renders four items with no Compliance link in the bar, while the Library menu holds Compliance, Docs, legal library, templates, print-ready documents, FAQ and changelog; the merged list renders six rows in urgency order (urgent Form B → EPR → AGM, then the setup steps) with its overflow count; the breadcrumb on /documents reads “Print-ready documents”; and there is 0 horizontal overflow at 390px and 1440px in light and dark.
- v0.3.20Added
- A custody page anyone can read: /custody. 0% custody was four words inside a popup — the strongest promise the product makes, and the hardest one for a council to check. The page walks the money through three steps (an owner pays → the council holds it → OpenStrata writes it down), lists what the software cannot do as impossibilities, lists what a council can check as buttons, and says plainly what is not live yet. Linked from the home page's 0% custody proof card and the footer, listed in the sitemap, llms.txt and site search, with 11 tests — including a guard that every catalog key the page references exists, because a mistyped key renders as a blank line. - Body prose stays English in src/lib/custody.ts, the same rule the print-ready documents and the user manual follow. A machine translation of “we never hold your money” is a materially false statement.
- Per-site receive labels on every payment. A rail invoice was labelled with the rail's own name (Lightning Network) — a string identical in every Give A Bit project, so a node operator or a council could not tell whose money had arrived. Every quote now carries OST northgate U302 pay-9142: site code, council, unit, request. - backend/src/rails/receive-label.ts is pure and deterministic, derived from keys the payment request already persists — so no migration, and no drift between the wallet, the node and the stored row. 21 tests. An explicit family site-code table (OST / SATO / TAD / MOTO …) makes a collision a visible edit rather than a coincidence of spelling. assertReceiveLabelFor refuses a foreign label loudly at the rail seam. - The quote API returns receiveLabel and the checkout panel shows it beside the payment instructions, so the string a council writes on a transfer is the string the node records.
Changed- The pre-rebrand raster logo is gone. /pitch was the last page still shipping the old artwork; it now uses the same mark-on-navy plate as the favicon, the PWA icons and the printed letterhead.
- Link previews point at a new 1200×630 /og.png — the old 237×377 logo was upscaled and cropped by every crawler — and twitter:card is now summary_large_image. npm run icons emits it.
Fixed- static/icon.svg could not be rasterised. Its comment contained a double hyphen, which is a hard XML error, so the rasteriser refused the file. The mark had never actually been rendered from that source until og.png needed it.
- A malformed changelog front-matter line (project: openstrataversion_history:) had swallowed the project name.
Verified- npm run check → 0 errors, 0 warnings. npm test → 164 passed (was 143). Backend: typecheck clean, 214 passed (was 191).
- audit:i18n → 886 keys × 9 locales, parity green. audit:contrast → 116 pairs, all at or above floor. Build green; /custody prerenders and the sitemap lists it.
- v0.3.19Changed
- The intro video's idle frame is a poster now. Kimi delivered a Cam-approved 1920×1080 thumbnail (static/video/openstrata-intro-poster.png, “Govern yourself.”) and the greeter popup's <video> points at it via poster=. Without it the player idled on a blank first frame, which reads as broken rather than paused.
Verified- npm run check → 0 errors, 0 warnings. npm test → 143 passed.
- Build green; the poster is emitted to build/video/openstrata-intro-poster.png (852 kB) next to the video.
- v0.3.18Added
- The new brush mark is the site's icon now. One master vector (static/icon.svg) plus a small-size rendition (static/favicon.svg), and scripts/generate-icons.mjs (npm run icons) rasterises the rest: favicon.ico, icon-192.png, icon-512.png and apple-touch-icon.png. The same mark is drawn inline by BrandMark.svelte in currentColor, so the header, the app sidebar, the footer, the auth card and the error page all carry the real logo instead of three CSS bars in an orange plate.
- /documents — print-ready documents. Meeting notice, meeting minutes, Form B (Information Certificate) and Form F (Certificate of Payment), each rendered as a white paper sheet with a letterhead, a reference code, a meta table, disclosure tables and signature lines. One printable document per page when the whole set is printed.
Changed- The header navigation is grouped. Twelve flat links needed ~1490px inside a 1232px bar, so the strip scrolled internally on every ordinary laptop and hid half the site behind an invisible scrollbar. Four inline destinations (Dashboard, Strata Tool, Compliance, Docs) plus two menus (Library, Company) bring the top level down to ~500px. src/lib/nav.ts is now the one authoring home: the flat list the footer and breadcrumbs read is derived from the grouped structure. The mobile drawer mirrors the same grouping.
- The desktop nav starts at 1280px, not 1024px. Between 1024 and 1280 the bar still could not fit six items alongside the actions cluster, so below xl the grouped drawer and the floating bottom dock carry navigation instead of a bar that clipped its own edges.
Fixed- Dark mode's selected states were invisible. bg-brand-50 / bg-brand-100 are tints used ~25 times as a selected-state plate (chosen jurisdiction, chosen bylaw pack, active decision pill), normally paired with a brand or slate label. Neither step was ever remapped for dark mode, so the plate stayed near-white (#ecfeff) while its label stepped up to brand-200 (#a5f3fc) or slate-800 — about 1.2:1. Both steps now have dark values, and the green “brokerage” accent gets its own pair. audit:contrast gained a tint-pair section so this cannot come back.
- .marketing-mobile-nav was missing from the print hide list, so on a phone the floating bottom dock printed across the foot of every page.
Verified- npm run check → 0 errors, 0 warnings. npm test → 143 passed (was 115).
- npm run audit:i18n → 872 keys across 9 locales. npm run audit:contrast → 116 pairs, all at or above floor.
Known issues- The backend is still not deployed. The website is live; the money-handling server has never run on a real host. That remains the gate on Phase 3.
- THOR's bitcoind has no wallet loaded (listwallets = []) — Cam has greenlit createwallet, but it has not been created yet.
- v0.3.17Added
- Frontend: per-tab hero artwork — src/lib/components/HeroArt.svelte draws one of six on-brand motifs (modules, chain, scales, ledger, network, signal) inside the shared .page-hero band, mounted on 14 pages via scripts/wire-hero-art.mjs. The motif is chosen by what the section *is*, not by rotation: /tools gets the module lattice, /roadmap and /spec get linked proof blocks, /compliance and /legal get statutes and a balance, /docs and /templates get rules documents, /about, /blog and /design get the community graph, /faq, /rss and /thank-you get answers radiating out. Drawn in currentColor only, so it inherits the band's brand tint and flips with the theme; decorative (aria-hidden), masked toward the edges, and hidden below 900px so it can never sit under a headline or add phone overflow.
- Frontend: in-app setup checklist on the dashboard (src/lib/components/SetupChecklist.svelte + src/lib/setup.ts). Four steps between a new workspace and a building that actually runs — add your units, open the two funds, load your bylaws, close your first month — each with a deep link, a progress meter, a dismiss control and a restore button. Progress is localStorage-only: the workspace is the record of truth, the panel is only a nudge. Corrupt, partial or hostile storage degrades to "nothing done" rather than throwing, and the dismissal is stored under its own key so an older build cannot resurrect a dismissed panel.
Changed- Frontend: light-mode state colours get the same split the orange already had. Text usages ride new documented steps — --success-text #0b6b49, --warning-text #8a5209, --danger-text #b3261e, --bitcoin-text #9a5700 — plus .text-* overrides for success, warning, danger, bitcoin, slate-400 and slate-500 in both themes, following the repo's established text-only-override convention so solid fills and tinted chips are untouched.
- Frontend: light-mode slate ramp is now stated in src/app.css instead of inherited from Tailwind's stock palette. The values are identical to stock, so nothing moves visually — but an undefined token cannot be audited, and these were exactly the steps the audit was silently skipping.
Fixed- scripts/audit-contrast.mjs silently skipped any token with no value in src/app.css. Because the light-mode slate ramp and every semantic status step live in Tailwind's stock palette rather than ours, --color-slate-400 was never audited in light mode — where it was rendering meta text at 2.90:1 on paper (and --color-slate-500 at 4.04:1 on a surface-3 chip). Measured in a real browser, text-success chips sat at 2.18:1, text-warning at 1.99:1, text-danger at 3.76:1, text-bitcoin at 2.30:1, and white on a solid #f7931a Bitcoin fill at 2.30:1. All fixed above; the audit now fails loudly if a token cannot be resolved.
- Frontend: two bg-bitcoin text-white buttons (QR pay, 3-of-5 broadcast) and one bg-danger fine button were relying on that unreadable pairing — the Bitcoin fills now take dark ink and the danger button uses --color-danger-solid.
Verified- npm run check → 0 errors, 0 warnings; npm test → 115 passed (14 files, +13 setup and +7 changelog tests); npm run audit:i18n → passed, 860 keys × 9 locales; npm run audit:contrast → passed, 106 token pairs; npm run build → green.
- Browser sweep (Chromium, production preview): 16 pages × light + dark = 32 combinations, 0 horizontal overflow and 0 contrast failures attributable to the changed tokens; hero art present on all 13 hero pages. Then 3 viewports × 2 themes × 4 pages = 24 combinations at 390×844, 768×900 and 1221×738 with 0 page overflow and 0 checklist overflow; the header artwork is display:none at 390 and 768 as designed and visible at 1221.
- v0.3.16Added
- Frontend: branded page header band (.page-hero in src/app.css). Every top-level tab now renders the same header language — a brand wash, a fine drafting grid faded toward the edges, and a brand hairline along the top edge — mixed entirely from theme tokens. All 14 hand-rolled per-page gradients are replaced (scripts/migrate-page-hero.mjs). Five of them ended in to-white (from-brand-50 via-white, from-amber-50/50 to-white), which painted a bright band straight across dark mode; that class of bug is now impossible because the band cannot reference a fixed surface.
- Frontend: StartHere.svelte — a three-leg journey strip (Explore the modules → Configure your building → Register and go live) mounted under the header band on /tools, /tools/wizard, /thank-you and /docs/manual/getting-started. Visited legs tick off, the current leg is highlighted, and a progress meter shows the whole path. Progress is localStorage-only — no account, no network call.
Changed- Frontend: brand ramp steps 600/700/800 retuned from the stock cyan (#0891b2 / #0e7490 / #155e75) to #0f6d89 / #0b5a72 / #08485c. --color-brand-600 is used as a *fill* 72 times over, and white on the old #0891b2 measured only 3.68:1; it is now 5.88:1. --color-brand-500 and below stay stock — those are the accent/glow steps.
- Frontend: --muted #6d7a82 → #5e6b75 and --faint #9ca8ae → #7d8890 in light mode.
Fixed- Ten real contrast failures found by the new audit, all fixed. --faint measured 2.42:1 on white while driving 9–10px uppercase eyebrow labels → now 3.6:1 against a documented 3:1 floor for that decorative token. --muted was 4.41:1 on white and 3.9:1 on a surface-3 chip → now clears AA on canvas, paper and chips. text-brand-600 / text-brand-700 were 3.27–3.68:1 on light and 2.67–3.29:1 on dark → text usages now ride the ramp (brand-700/brand-800 light, brand-300/brand-200 dark) via new .text-brand-* rules, referencing palette variables so the green "brokerage" accent theme swaps with them. White on --orange measured 2.77:1 — the brand coral is a glow token, not a fill, so solid orange fills now use new --orange-solid / --orange-solid-deep (5.02:1 with white), and the coral is kept for icons, rails and dots. Applied across .primary-button, .signin-button, .mobile-nav .mobile-add, EmptyState, AuthModal and Tour.
- Frontend: two mid-page bg-gradient-to-b from-slate-50 to-white bands (/pitch, /about) also painted white in dark mode → to-transparent.
Verified- npm run check → 0 errors, 0 warnings; npm test → 95 passed (12 files, +10 journey tests); npm run audit:i18n → passed, 820 keys; npm run audit:contrast → passed, 60 token pairs; npm run build → green.
- Browser sweep, 17 pages × light + dark = 34 combinations, 0 contrast failures and 0 horizontal overflow. Contrast was computed against the live DOM with a real WCAG composite (alpha-blended ancestor surfaces, oklab() and color(srgb …) parsed), probing h1/h2/p/links/buttons/table cells at their actual font sizes and boldness — not from the stylesheet alone. StartHere renders 3 legs on all four journey pages; .page-hero is present on all 14 migrated pages.
- v0.3.15Added
- Frontend: the greeter popup on / now plays the delivered intro video. Step 1 of the first-run tour renders /video/openstrata-intro.mp4 (Kimi's HyperFrames render, 57s, 1920×1080, committed at static/video/openstrata-intro.mp4) in a real <video> element with inline controls; if the asset ever fails to load the card falls back to the honest placeholder copy instead of a broken frame.
- Frontend: “What you get” — four plain-language offer facts inside the popup (0% custody, BCFSA-aware compliance, a Bitcoin proof trail for every action, portable history) — and “Where to start” — a three-step instruction list. Both sit beside the video.
Changed- Frontend: step 1 of the tour is now a wider two-column card (video left; facts + start steps right) and steps 2–4 stay narrow. Previously the card was single-column and 934px tall, so a 738px viewport hid 255px of it — including the Next button. Now: 720×522 with zero hidden overflow at 768px+ and at 390px, verified in a real browser on all four steps.
- Frontend: the video section and the offer copy appear on step 1 only, so steps 2–4 stay lean.
Fixed- i18n: npm run audit:i18n was failing on main — the v0.3.14 commit added the four video keys to English, French, Spanish, Chinese, Hindi and Filipino but never to Polish, Ukrainian or Swahili, and the locale-parity guard checks every locale against French. The missing keys are now translated, so the audit passes at 813 keys × 9 locales. This would have failed the frontend verify CI job.
Verified- npm run check → 0 errors, 0 warnings; npm test → 85 passed; npm run audit:i18n → passed, 813 keys; npm run build → green (the video is emitted to build/video/openstrata-intro.mp4).
- Browser-verified (Chromium, production preview): all 4 tour steps, 0 hidden overflow and 0 horizontal page overflow at 1221×738, 768×900 and 390×844; step 1 resolves /video/openstrata-intro.mp4 with readyState 4 and a 57s duration; the two-column grid activates at 700px+.
- v0.3.14Added
- Frontend: first-run greeter popup card rebuilt with a video section (src/lib/components/Tour.svelte): greeting card (icon + scene art + eyebrow/title/body) + a video section inside the same card, so a new visitor gets hello + one-minute intro in one glance. Video section: a tease frame (play mark + short subhead + fallback hint + CTA) collapses to a single trigger line when not expanded; expanded it holds either a real <video> once the asset lands or an honest "Video coming soon — back in a few days." placeholder.
- i18n: new video-section keys — tourVideoTitle, tourVideoSub, tourVideoCta, tourVideoFallback (English for now).
Changed- Version bump to v0.3.14 across root + backend package.json, both lockfiles, CHANGELOG.md, docs/MISSION.md, docs/EXECUTIVE-SUMMARY.md, docs/WORKPLAN.md, .ai_docs/current-status.md, LATEST-UPDATE.md.
Verified- npm run check clean; npm test green; popup renders on / for signed-out fresh visitors; video section inside the greeting card, inline, does not break the 4-step tour.
- v0.3.13Added
- Backend: Ziggy PSBT workflow seam (broadcastPsbtWorkflow in backend/src/ziggy/node-broadcast.ts) — the BIP174 node path: walletprocesspsbt (sign) → finalizepsbt (extract when complete) → sendpsbt → txid. The signing coordinator's aggregated psbtB64 passes straight through; otherwise a deterministic BIP174-shaped skeleton is serialized from the plan (serializePsbtSkeleton: global unsigned-tx map + per-input partial-sig entries). Readiness guard: the seam refuses below-threshold plans before any RPC (same contract as broadcastPsbt).
- Backend: /treasury/psbt/broadcast tries the workflow seam first on the rail path; the raw-tx seam (sendrawtransaction, railEnabled: true) is the watch-only fallback. Both failing → txid: null + rail: 'unavailable' + placeholder: false — the broadcast-honesty tri-state is preserved.
Fixed- Pre-existing typecheck error in rosa/ingest-vector.ts (TS5076) and the stale broadcastRawTx throw test (both also fixed independently by the family; kept through the rebase).
Verified- backend npm run typecheck clean; backend npm test 191 tests (was 182; bitcoin-modules 26), e2e smoke 6 skipped (no DB).
- v0.3.10Added
- Backend: Rosa corpus → pgvector indexer (backend/src/rosa/ingest-vector.ts) — embeds the in-memory BC corpus with Ollama /api/embeddings (nomic-embed-text, 768 dim) and upserts into corpus_chunk (migration 0002), idempotent per citation; CLI rosa index (embed + write corpus, needs Ollama + DB) + rosa reset (dev: drop + re-create corpus_chunk). Pure noun-phrase placeholder embed mode (ROSA_EMBED_MODE=pure or no OLLAMA_BASE_URL) lets rosa index populate corpus_chunk now before Ollama is provisioned — proving the retriever + indexer + query path end-to-end against the BC corpus with a real pgvector cosine search.
- Backend: Ziggy on-chain broadcast plug-in seam (backend/src/ziggy/node-broadcast.ts) — bitcoind JSON-RPC raw-tx path first (sendrawtransaction → txid; PSBT workflow seam walletprocesspsbt → finalizepsbt → sendpsbt is the next step). broadcastRawTx(plan, btc, outputs) → { txid, hex }; returns a deterministic placeholder txid (psbt:<planId>:<shortHash>) when no node client is reachable so the rest of the seam (UI, receipts, reconcile) can iterate now; real node client overrides it when configured. planSummary(plan) helper.
Fixed- broadcastRawTx returned null txid when no node client was reachable — now returns a deterministic placeholder txid so the rest of the seam can iterate.
Known issues- Phase 3 backend not yet deployed on a Tailscale host (the e2e smoke gate covers the Postgres adapters; a live host run is still pending).
- Rosa answers with pgvector+Ollama embeddings only after Ollama is provisioned and rosa index has run; until then the keyword fallback answers with citations.
Added- Reusable Card component (src/lib/components/Card.svelte) with content/compact/hero variants; migrated 47 content cards across 9 route pages (about, blog, docs, legal, templates, compliance, pitch, roadmap, spec, tools, rss). Structural cards (tables, accordion wrappers, the design page's raw-class demo) intentionally remain glass-card
- Marketing typography ramp: h1 36px / section h2 24px / sub-section h2 20px, card titles weight-800 (scoped .mesh-bg .glass-card) across all 13 marketing pages
Changed- Dashboard rail + marketing cards unified on one token set: .panel padding 17px→24px, weight-800 headings, shared --shadow-card; SatohashStatus third visual language eliminated
- Sidebar shell pinned to the viewport (top:0 + max-height:100dvh); HostConnect strip moved into the dashboard's main column so the nav footer button stays visible at every size
Fixed- Site-wide Tailwind v4 cascade bug: unlayered element resets (a{color:inherit}, button{border:0;font:inherit}) were beating the layered .text-*/.border-* utilities, so every brand link rendered plain ink (invisible in dark mode) and buttons lost size/border. Moved the resets into @layer base so utilities win
- text-bc-blue on dark cards was ~2:1 (dark navy tuned for light surfaces); lightened dark-mode text usages only (solid bg-bc-blue badges keep the navy behind white text) to 6.9–9.0:1
Verified- Real browser (Chrome + CDP): brand links teal #0891b2 in both modes, dark body #e2e8f0, bc-blue chips 6.9–9.0:1, zero horizontal overflow on all 13 pages × 3 sizes (light + dark). svelte-check 0/0, 85 tests, build clean
- v0.3.9Added
- Backend: Rosa pgvector/Ollama retriever (backend/src/rosa/vector-retriever.ts) — real pgvector + Ollama retriever on the existing Retriever contract; embeds the question with Ollama /api/embeddings (nomic-embed-text, 768 dim), cosine-nearest-neighbor searches corpus_chunk (migration 0002, vector(768), HNSW) via pgvector <=>, falls back to keywordRetriever when pgvector/Ollama are unavailable. POST /api/v1/rosa/query + /rosa/sources unchanged; response carries collection so callers can tell which tier answered.
- Backend: Ziggy PSBT broadcast + on-chain reconcile seam (backend/src/ziggy/broadcast.ts) — broadcastPsbt (marks a ready plan broadcasted; refuses below threshold) + postSpendToLedger (debits the authorized fund on the trust ledger so the on-chain leg reconciles into the same hash chain). Wired as POST /api/v1/treasury/psbt/broadcast (treasurer+); /treasury/psbt/plan unchanged.
Changed- docs/ROADMAP.md + docs/WORKPLAN.md: Phase 3 items marked complete.
- .ai_docs/current-status.md, docs/KIMI-HANDOFF.md, LATEST-UPDATE.md: refreshed.
Fixed- Tailwind v4 unlayered-reset cascade bug (already in 0.3.9 code, documented here for the release marker).
- v0.3.5Added
- Per-council DB-backed unit registry (migration 0005_council_units.sql, unit table keyed on (community_id, unit_ref))
- UnitStore interface + PostgresUnitStore + MemUnitStore (list/get/upsert/remove/seedDefault); register seeds each new council's building
Fixed- PostgresPaymentRequestStore.markStatus wrote the referenceCode into status (status = $2 instead of $3) — the e2e re-quote assertion would have failed on a real Postgres
- Payment-quote unitRefs now canonicalize at the boundary (unit-302 → 302) so stored rows, referenceCodes, and unit detail agree
Changed- Version markers synced to v0.3.5 (root + backend package.json); backend now 167 tests / 16 files, frontend 45 tests, i18n 565 keys × 9 locales
- v0.3.4Added
- 20-item upgrade push: auth rate limiting (#6), build-time CSP pinning (#2), monthly treasury series endpoint (#4), live CAD/BTC rate provider (#7), meetings quorum/voting UI (#8), transparent sub-accounts dashboard (#10), CSV bank-feed import (#12), war-chest DCA planner (#18), PSBT 3-of-5 orchestration seam (#15), Satohash stamp endpoint (#19), watch-only xpub import (#16), portable export (#20), CRT evidence bundle (#11), print-ready Form B/F (#9)
- v0.3.3Fixed
- CSP in static/_headers was blocking the page's own assets (Google Fonts, Umami analytics, live-API connect-src); fonts now load once, unused Inter dropped
Changed- Landing shell tightened (1280px content, crisp 2-layer card shadows) per "solid, tight" pass
- v0.3.2Added
- src/lib/api/ client (config/token/client/auth + typed ledger/units/rails helpers), AuthModal, Live/Demo pill, dashboard + tools + reconciler + pitch wired to /api/v1/* with demo fallback
- v0.3.1Added
- Zero-dependency JWT auth + multi-tenant council scoping (migration 0004), roles admin/treasurer/member, tenant-isolated payment store, Postgres e2e smoke suite + CI job
- v0.2.6Added
- Migrated remaining hard-coded interface copy across all 14 routes (dashboard footer, wizard, about, blog, templates, legal, roadmap, rss, spec, compliance, tools, docs, pitch) to the shared locale catalog — 458 keys total
- First reviewed locale overrides for French (fr-CA), covering all interface copy added in v0.2.6
Changed- Version markers (layout, pitch, wizard config) derive from package.json v0.2.6
- v0.2.5Fixed
- Centralized visible, metadata, pitch, and generated-config version markers on the package version
- Removed stale v0.2.0 and 0.1.0 release identifiers from active application surfaces
- v0.2.4Fixed
- Synchronized package-lock.json with package.json so Cloudflare Pages can complete npm ci
- Added a versioned deployment repair release after stale production output was detected
- v0.2.3Added
- Locale-aware date, number, and CAD currency formatting for supported languages
- npm run audit:i18n translation completeness audit for route catalog usage
Changed- Dashboard, About, Pitch, Blog, and shared footer now respond to the active locale for visible formatted values
- v0.2.2Added
- Reusable /templates library for legal, governance, and finance workflows
- Source and professional-review notes on every starter template
Changed- Finished remaining visible wizard and compliance interface copy through the shared locale catalog
- Added /templates to the site navigation
- v0.2.1Added
- Shared locale coverage for the public blog interface
- Source-linked /legal library for BC legislation, regulations, official guidance, and tribunal information
- v0.2.0Added
- Responsive OpenStrata operations dashboard with desktop sidebar and mobile navigation
- Multilingual shell for English, French, Spanish, Chinese, Hindi, Filipino, Polish, Ukrainian, and Swahili
Changed- Rebuilt the missing SvelteKit source foundation for the current GUI
Fixed- Added accessible interactive card and dialog semantics
- v0.1.0Added
- Initial project scaffold
- Project documentation from canonical TEMPLATE (8 files)
Changed- None
Fixed- None
Earlier releases
These shipped before every release kept a full section. The one-line summary is what was recorded at the time.
- v0.3.12 Fix the demo notice layout on phones: the flex-1 text column next to shrink-0 action buttons collapsed to a 13px-wide sliver at 390/430px (a 604px-tall notice), so the strip now uses its own flex classes with a real flex-basis and the buttons wrap to their own line. Text measure 268-338px at 360/390/430, no overlap; svelte-check 0/0, 85 tests, i18n audit clean.
- v0.3.11 Visitor-honest demo mode: the dashboard no longer greets the public with a build-configuration prompt. The demo notice now states plainly that every community, balance and action on screen is sample data, offers one obvious way to request access, and keeps the operator path as a quiet 'Running your own host?' link documented in README.md; 6 new catalog keys x 9 locales, i18n audit 0 missing keys / 0 hard-coded-copy warnings.
- v0.3.8 Next 20 shipped — live flows, governance, Bitcoin & trust: RosaChat citation-only compliance Q&A, register wizard, QR scan-to-pay + wallet deep links, Forms B/F 7-day tracker, MyUnit panel, BallotEngine + minutes export, CRT-ready bylaw case file, statutory meeting notices, compliance health score, mempool.space balances, war-chest DCA planner with Form B disclosure, live CAD/BTC sparkline, rails readiness checklist, ledger chain visualizer, PWA offline + install, a11y 0-warning, tour/empty-state illustrations, host-connect strip; browser-verified zero overflow 390→1280, 78 frontend tests, i18n 893 keys x 9 locales
- v0.3.7 All 20 user-flow/GUI/Bitcoin improvements shipped: backend member registry (migration 0006) + /members, verified /ledger/entries, /deadlines; frontend CheckoutFlow pay-fees + receipts, MonthlyClose, BylawCase, MemberWorkspace, DeadlinesPanel, brand-accent theming + /design page, RailsStatus, SigningRoom, WalletPanel, LedgerExplorer, ExportCenter, MemberManager, NotificationsFeed, RateBadge, glossary + illustrations + print styles; mobile-verified zero overflow 390→1698
- v0.3.6 GUI & user-flow push — all 20 improvements shipped: SVG icon system, real glass-card tokens (was undefined), typography ramp, designed empty states, breadcrumbs, tools sub-nav, scroll-spy TOC, tool modules in the Cmd-K palette, mobile dock on every page, view transitions, shimmer skeletons, dynamic time-of-day greeting, metric sparklines, micro-interactions, first-run tour, confirm dialogs, error toasts + inline validation, last-synced chrome, and hero-pattern CTAs. Tablet-width header overflow fixed (jurisdiction picker moves to lg+).
Follow every release
Releases land in the RSS feed the moment they ship — no newsletter, no account.
Open the feed →