v0.3.0
Pre-release
Pre-release
Hardening and polish for opening the repository to visitors. A signing secret
that could be forged outside production is closed; the overview page works on a
phone and reads to a screen reader; the walkthrough is recorded in Thai as well
as English; and two more checks fail the build when the documentation drifts
from the code.
Security
- The API booted on the placeholder signing secret outside production.
backend/.env.exampleshipped a workingJWT_ACCESS_SECRET=change-me-…— 32
characters, so it cleared the length check — while the guard that recognises
the placeholder ran only underNODE_ENV=production, which that same file did
not set. A token signed with the published string was accepted on a
development boot, which binds0.0.0.0like any other, so a forged admin
token read the employee register over the network. The example now ships the
JWT secrets blank (the API refuses to start on a placeholder and names the
fields), and the placeholder and equal-secret checks run at every tier, which
is whatsecurity.mdalready described.
Fixed
- The five-minute demo in the README did not work on a clean clone. The
migratecompose service is whatnpm run db:seedandnpm run db:initare
documented to run through, and both boot Nest, which validates the whole
environment before any module starts — but the service passed no JWT secrets.
The seed stopped half way, leaving a console that contradicted the README,
anddb:initfailed outright. - Layout and contrast faults on the landing page: the hero headline overflowed
its column and painted across the product video, anchor jumps left section
headings under the sticky bar, the download button set its label in muted
grey on orange, the savings figure was drawn at 1.9:1 on the dark band, and
the page scrolled sideways at 320px. Every text and background pair now
clears WCAG AA in both themes. - The assistant reported itself as available when it was not (CW-038). Three
configurations reachedGET /configasassistantEnabled: truewhile the
module handed back the disabled provider, so both clients drew an assistant
that answered every question withASSISTANT_DISABLED: the provider name
openai-compatible, which validates but has no implementation anywhere in
src;ASSISTANT_PROVIDER=nonewith the assistant switched on; and the
Anthropic provider with no API key, outside production.spec.mdand
security.mdboth already stated the rule — an optional feature fails loudly
when switched on — but the check ran only in production and only for one
provider. If your.envhasASSISTANT_ENABLED=truewithout a usable
provider, the API now refuses to start and names what is missing. The
shippedbackend/.env.examplewas one such file, and is fixed.
ASSISTANT_EMBEDDING_PROVIDERhad the same shape and is restricted the same
way:openaivalidated, routed knowledge search through the vector path, and
returned exactly whatnonereturns, because the embedding call is not
implemented until CW-018. It acceptsnoneuntil that lands. - The landing page had no document head, so every phone laid it out at
around 980px and zoomed out. It now has a doctype, a language, a viewport,
a description, canonical and hreflang links, and Open Graph cards. - Every console screenshot on the landing page was stretched 93% too tall.
The width and height attributes on an<img>map to the CSS properties, and
the rule set onlywidth, leaving the attribute's height standing. - The Thai copy on the landing page read like a translation — calques
(ติดธง for "flagged"), transliterations (เอนจิน, สตาร์ท, ไมเกรชัน, เซสชัน),
literal renderings (หน่วยความจำถาวร, ฝั่งเครื่อง, โดยค่าตั้งต้น), English
passive voice, and one invented benchmark. Rewritten throughout. - A sign-in was refused when it landed in the same second as a forced
sign-out. The 0.2.0 fix covered only the account-creation write site; the
comparison itself still readiat × 1000 < sessionsValidFrom, so a password
change, a forced logout or a disabled account left every request refused with
"session has been invalidated" for up to a second, cleared by signing in
again. The comparison now happens at the whole-second resolutioniatcan
express, in one place shared by the JWT strategy and the MFA verify. It had
been surfacing in CI as an intermittent end-to-end failure. - The overview page could not be used from a phone, in two ways. The
language switch was hidden along with the section links below 880px, so a
visitor arriving on a phone — the audience the page is bilingual for — had no
way to reach the other language; and the console screenshots, given the full
column width, put the interface's own 14px labels under 4px on a 390px
screen. The switch now stays, and each screenshot links to its full-size
asset for the phone's own zoom. - The savings figure on the landing calculator wrapped mid-number for any
company over about eighty people — the one figure the section exists to show,
broken between two digits. It no longer wraps and shrinks to fit instead; the
English page had also askedIntlfor THB without naming the symbol and
rendered "THB" where the Thai page rendered "฿". - The recorded walkthrough was in the wrong typeface, and each caption named
the previous screen. The console asks for IBM Plex Sans Thai; inside the
recorder that request failed silently and the video fell back to Loma, two
scrolls above screenshots in the right face. And because the console is a
single-page app, the caption bar outlived each navigation, so for the settle
after every route change it described the screen just left. The recorder now
serves the font itself and waits for the destination before it captions. - A screenshot showed a state a default install cannot be in.
15-assistant.pngwas captured with the assistant switched on, directly under
a line saying it is off by default; it is replaced by the knowledge base,
which answers with no provider configured. A byte-identical duplicate
screenshot went too, andlanding/joined the repository-layout table. npm testfailed on a clean clone because naming theAPP_CONFIG
injection token pulled in the module that defines it, whose@Module
decorator validatedprocess.envat import time — so a unit test that touches
neither a database nor a config could not load. The token now lives in a file
of its own. This is a second clean-clone failure, distinct from the compose
one above.- The README told a reader running without Docker that pgvector was
optional. The first migration opensCREATE EXTENSION "vector", so a plain
postgres:16stops atprisma migrate deploy. CI and compose always ran
pgvector/pgvector:pg16; only the non-Docker note said otherwise. - Ten documented test counts were wrong, across six files in two languages:
two different claims — the domain layer, and the whole suite — had drifted
into one number. Corrected to the measured 246 domain and 288 backend unit. - The menu and theme buttons had no name a screen reader could read — each
was an icon-only glyph announced as "button", and on a phone the menu button
is the only way to navigate. Both now carry anaria-label, the menu also an
aria-expandedbound to its state, and the decorative glyph isaria-hidden.
Added
npm run verify:compose— checks in CI that every compose service which
boots application code is handed the environment the application refuses to
start without, derived from the real validation schema rather than a second
list that can drift.- An English landing page at
/en/, generated from the Thai one by
landing/build-en.mjsso the two cannot drift. A Thai string with no
translation fails the build. The Pages workflow regenerates it and fails on a
diff. - The landing page is published to GitHub Pages and linked from both READMEs.
npm run verify:docs— measures the backend suites and checks every
documented test count against them in CI; a number with two homes, or a claim
reworded past the pattern that watches it, fails the build.npm run verify:sql— the greppable tenant scoping ADR-0003 asks for:
every raw SQL statement insrc/is listed with why it is safe, and an
unclassified or stale entry fails the build.- A Thai-captioned walkthrough beside the English one, recorded from the
same script (docs/demo/record.mjs, now taking a language). The Thai overview
page shows the Thai take; the English page and the READMEs keep the English
one. - A social-preview card (
docs/social-preview/) — the 1280×640 image GitHub
serves when the repository link is unfurled, generated from an HTML card so it
can be regenerated when the screenshot or palette changes.
Changed
- Empty and error states now speak the monochrome glyph language the rest of
the interface uses, instead of colour emoji (📭 ✅ 🧾⚠️ …) that rendered
differently on every platform and read as placeholders. Each empty state now
shows the glyph of its own section. 24 icons across 20 files; no behaviour
change, as the icons were alreadyaria-hidden.