Spec: F22 — Landing Page
Status: shipped · Audited: 2026-07-15
1. Problem & CEO Job
The pitch before the product. A Vietnamese fit-out / renovation / small M&E contractor owner gives a landing page about eight seconds. In that window it must say one concrete thing in their own trade terms: paste the mess from Zalo and Excel, get today’s owner brief — ai đang nợ việc, khách nào cần chốt, việc nào trễ. The page leads with the F70 wedge — messy chat/spreadsheet in, today’s operating brief out — not a generic morning-AI promise and not AI buzzwords. Use the strategy-v2 §14.2 messaging formula: For [vertical owner], Lumi helps you [concrete outcome] without [current pain] — here, “không còn thất lạc trong Zalo và Excel.” (Banned per strategy v2 §14.1: “AI Chief of Staff”, “all-in-one AI workspace”, “Notion + Jira + Slack + AI” — never lead with these for the Vietnam vertical.) The page’s only success metric is qualified registrations.
2. User Stories & Acceptance Criteria
- As a visiting CEO, I want to grasp the value in one screen, so that I don’t have to study.
- Hero: one headline (the F70 wedge — paste the Zalo/Excel mess, get today’s owner brief), one subline, one primary CTA →
/register. No carousel, no autoplay video. - The brief itself is the hero visual — show a read-only preview modeled on real Brief item sections and a registered Lumi action, not abstract illustrations or an action the product cannot perform.
- Hero: one headline (the F70 wedge — paste the Zalo/Excel mess, get today’s owner brief), one subline, one primary CTA →
- As a visitor, I want the page in my language —
videfault for the market,enswitchable (landingnamespace); when registering, the selected locale is handed off optionally to the existing account profile. - Logged-in users hitting
/can reach the app in one click (or are redirected) — returning users never re-read marketing. - Page is public (no auth) — logged-out users see the full landing page, only logged-in users redirect to
/work/today. - Page is fast (no app-bundle hostage) — the default production
/store/:langdocument contains the prerendered experience but does not execute the React workspace graph. It loads one content-hashed enhancement runtime for returning-session redirect and supported-locale navigation. Performance budget: LCP < 2.5s, FCP < 1.5s, JavaScript < 150 kB gzip. - The production
build:ssgartifact preserves the prerendered landing markup and metadata without serializing preview-server URLs or runtime-injected third-party scripts into the deploy document. It preloads only locale-critical Geist subsets, and the canonical CWV harness refuses to grade Landing unless that deploy artifact exists. - Page is responsive at 320–1920 — uses Tailwind responsive prefixes, single-col→two-col grid, responsive font sizing.
- Both landing variants expose the official Lumi App Store and Google Play destinations in their footer without displacing the primary registration CTA.
3. Scope Boundaries
- In: single page: hero (F70 wedge — paste Zalo/Excel mess → today’s owner brief), how-it-works (paste the mess → Lumi reads it → owner brief, 3 steps), social proof placeholder, CTA, footer with official native-download links; progressive-static production delivery for the localized default document; returning-session and locale enhancement; landing CWV and actual-document bundle evidence against the same SSG artifact Cloudflare deploys.
- Out: blog/SEO machine, pricing page (until F24 ships), multi-page marketing site, analytics-driven A/B infrastructure.
4. Data Model
n/a. Public entry / resolves a returning browser session first: authenticated visitors enter /intel/chat, while anonymous visitors redirect to localized store route /store/:lang. An authenticated direct store visit redirects to /work/today. No dedicated backend contract beyond standard auth redirect behavior.
5. API Contract
n/a. Public entry / → TanStack beforeLoad session decision → /intel/chat or /store/:lang → LandingPage.tsx. No dedicated backend contract beyond standard auth redirect behavior.
6. UI / UX
- Same design system as the app (DESIGN.md) — the landing must look like the product it sells; a style break reads as bait-and-switch.
- TASTE.md applies hard: no stock gradient-blob hero, no generic SaaS template smell.
- i18n:
landing.
7. Test Plan
| # | Case | Type | Where |
|---|---|---|---|
| 1 | Renders, CTA navigates to register | e2e | describe("Landing") |
| 2 | Authenticated / enters the workspace; authenticated direct landing redirects to /work/today | e2e | describe("Landing") + static runtime unit |
| 3 | Visual at key widths | e2e | screenshots 01-landing/ |
| 4 | Locale parity | unit | i18n test (F18) |
| 5 | Prerender sanitation removes preview replay artifacts and replaces the default landing workspace graph with one content-hashed enhancement runtime while preserving metadata and root markup | unit | src/test/store/prerenderHtml.test.ts |
| 6 | Landing CWV artifact custody rejects a plain SPA build and accepts dist/store/en/index.html | unit + harness | src/test/performance/cwvArtifactCustody.test.ts; scripts/measure-cwv-p0.mjs |
| 7 | Built landing document executes only its enhancement runtime, retains native CTA/FAQ behavior, changes supported locale by full navigation, and contains no preview/workspace graph request | production-browser | tests/e2e/prerender-runtime.spec.ts |
| 8 | Enhancement preserves returning-session redirect and rejects mutated unsupported locale values | unit | src/test/store/landingRuntime.test.ts |
| 9 | Locale-critical Geist subsets are preloaded without unrelated locale fonts | unit + artifact browser | src/test/store/prerenderHtml.test.ts; tests/e2e/prerender-runtime.spec.ts |
| 10 | Actual deployed landing document passes the 150 kB JavaScript budget | unit + build gate | src/test/_shared/bundle-budget.test.ts; build:ssg document gate |
| 11 | Current and classic landing footers link to the exact official App Store and Google Play listings | unit | F22.AC001.DOWNLOAD in src/test/store/LandingPageRoute.test.tsx |
8. Current Behavior
Shipped. Landing page exists as the public entry surface, with / resolving the returning browser session or redirecting to localized marketing route /store/:lang. Functional E2E proves CTA → /register and authenticated entry. Browser credentials remain in an HttpOnly refresh cookie; a non-secret user_email preference hint primes routing while the session validates, and a logout tombstone prevents stale redirects. LandingPage remains lazy for development and SPA fallback use, but Cloudflare’s default localized landing documents do not boot that graph: pnpm build:ssg captures the rendered page, removes workspace modules, injects one content-hashed enhancement runtime, and preloads locale-critical Geist fonts. The owner-Brief preview remains fully static with no API dependency, but landingBriefPreview.ts owns its semantic entries: a task, a risk, and the registered mark_deposit_paid Lumi action. The landing translates only their copy keys. A deterministic parity proof passes the two Brief items through the same sectioning function as the shipped Brief and prevents the preview from regaining the unsupported bulk-approval affordance removed on 2026-07-19.
The v2 and preserved v1 footers share StoreDownloadLinks, which owns the exact official Lumi App Store and Google Play destinations. The links use the eagerly available common namespace for stable store-brand labels and wrap at narrow widths instead of widening the page.
Two landing versions live in parallel (A/B comparison, 2026-07-05)
Two landings ship side by side so we can see which one converts better before committing to one:
| Version | Component | Route(s) | Copy source | i18n namespace | Framing |
|---|---|---|---|---|---|
| v2 (default) | pages/store/LandingPage.tsx | / · /store/:lang | docs/marketing/landingpage.md | landingV2 | ”Run your company by talking to it.” — assistant-first / chief-of-staff (F91) |
| v1 (archive) | pages/store/LandingPageV1.tsx | /store/:lang/v1 | (original) | landing | F70 wedge — paste Zalo/Excel mess → owner brief |
- Both are public, code-split (
lazyWithPreload), and self-contained: v1’s nav/footer/language-switcher/canonical stay inside/store/:lang/v1; v2’s stay on the default/store/:lang. v2 carries a discreet “Classic page” footer link to v1; there is no auto-redirect between them. - Comparison method: for now the split is by explicit URL (share
/store/:lang/v1vs the default) — no bucketing or analytics assignment layer yet. The success metric is unchanged from §1: qualified registrations. Open decision: wire per-visitor bucketing + a conversion event before calling a winner, then retire the losing version and its namespace (tracked in §12). - Both versions must stay green on the same gates (route-parity guard, F18 key parity, landing CTA E2E). When v2 changes copy, update
landingV2locales; v1’slandingnamespace is frozen unless the archive itself is being corrected.
9. Error States
| # | Error | Response | Notes |
|---|---|---|---|
| ES1 | CTA registration route is undefined | Router not-found surface | CTA route and browser journey are contract-tested |
| ES2 | LanguageSwitcher i18n-not-ready race | i18n.changeLanguage throws if called before init | No test for i18n-not-ready scenario |
| ES3 | Lazy import of LandingPage fails in development/SPA fallback | TanStack Router error boundary | Production default landing does not depend on this import; fallback failure proof remains absent |
| ES4 | Missing vi keys for new hero section | i18next renders key name instead of vi text | F18 parity test catches this only if both locales updated |
| ES5 | Auth redirect misfires despite a returning-session hint/cookie | Authenticated user lands on marketing instead of the workspace | Root browser journey and static-runtime session-hint unit proof cover both entry boundaries |
| ES6 | Footer link broken | 404 on footer route (e.g., /privacy) | Landing E2E only checks primary CTA and redirect |
| ES7 | Prerender snapshot replays runtime-injected assets | Preview-origin preloads fail in production and duplicate third-party scripts compete with the LCP image | Sanitize the captured DOM before writing and fail a deterministic artifact test on forbidden URLs/scripts |
| ES8 | CWV run grades pnpm build instead of pnpm build:ssg | Registry reports an artifact Cloudflare never serves | The canonical runner exits before opening Chromium when the localized SSG document is absent |
| ES9 | Static enhancement accepts a mutated unsupported locale | Navigation reaches a non-canonical store path | Runtime checks the selected value against the document’s supported options before navigating |
| ES10 | Geist swaps after first paint | Hero/nav reflow pushes CLS above 0.1 | Build injects only the active locale’s critical 400/500/600/700 preloads; floor CLS proof is 0.000 |
10. Diagrams & Docs Touched
frontend/DIAGRAMS.md route tree.
11. Implementation Audit (2026-06-11)
| # | Claim | Status | Evidence |
|---|---|---|---|
| 1 | Hero: one headline, one CTA → /register; no carousel/autoplay | ✅ match | LandingPage.tsx; TASTE.md compliance; E2E CTA test |
| 2 | Logged-in / → redirect to /work/today; logged-out → full landing | ✅ match | <Navigate to="/work/today" replace /> in LandingPage; describe("Landing") E2E |
| 3 | LandingPage is React.lazy()-imported (code-split) | ✅ match | App.tsx lazy import; bundle inspector |
| 4 | vi default, en switchable via landing i18n namespace | ✅ match | F18 key parity test covers landing namespace |
| 5 | Functional E2E: CTA → /register + authenticated redirect | ✅ match | Added 2026-06-11; describe("Landing") |
| 6 | Anonymous landing locale is not proven across devices | ⚠️ gap | Registration now sends the optional locale to User.language; the second-device browser journey and commit-bound runtime evidence remain absent |
| 7 | Static hero preview remains semantically aligned with real Brief output | ✅ match | landingBriefPreview.ts uses Brief item shapes plus registered mark_deposit_paid; F22.AC001.PREVIEW_PARITY exercises real sectioning and localized copy; F22.AC001.PREVIEW_ACTION proves the backend command remains registered and confirm-only; route proof rejects the unsupported “Approve all” affordance |
| 8 | Authenticated users with a stored token are redirected before landing content renders | ✅ match | useAuthStore initializes isAuthenticated from localStorage synchronously; LandingPage returns <Navigate to="/work/today" replace />; / first routes through StoreLocaleRedirect but the localized landing still redirects immediately |
| 9 | Hero copy reframed to the F70 wedge (paste Zalo/Excel mess → today’s owner brief) per strategy v2 §14.2 | ✅ match | 2026-06-18 visual QC: LandingPage.tsx hero now leads with Zalo/Excel/site-update input → owner brief output, and the hero visual is an in-product brief preview rather than remote decorative photography. Locale keys updated in landing en/vi. |
| 10 | First viewport shows the product promise and a hint of the next section | ✅ match | 2026-06-18 visual QC: hero min-height reduced from full-screen gulf to 78dvh and right-side owner brief preview occupies the desktop first viewport; screenshot 01-landing/01-home.png is the verification artifact. |
| 11 | Production SSG snapshot has no preview-origin or replayed analytics tags; CWV grades the SSG document | ✅ match | scripts/lib/prerender-html.mjs; scripts/lib/cwv-artifacts.mjs; focused Vitest + production Playwright proof; 2026-07-15 deployed-artifact measurement |
| 12 | Production default landing does not execute the workspace graph and passes the actual-document JS budget | ✅ match | One hashed enhancement module, no modulepreloads; document gate 0.5 kB gzip and empirical transfer 0.7 kB |
| 13 | Overall visual quality has a maintained human-approved baseline | ⚠️ gap | Responsive overflow journeys and screenshots pass, but no approved diff baseline/sign-off exists |
Summary: 11/13 match; 2 explicit gaps (cross-device locale proof and human-approved visual baseline).
Edge Cases Catalog (formerly §11, 2026-06-12; E1–E10): Key items integrated above. Remaining: E1 authenticated redirect to /work/today (✅ browser-covered); E2 static hero no API dependency (✅ by design); E3 slow image loading (fixed container + high-priority preload; floor LCP covered); E5 scroll-reveal on slow devices; E6 long CTA text overflow on mobile; E7 SEO metadata in the deploy document (✅ artifact-covered); E8 footer links not exhaustively tested; E10 separate default-landing runtime (✅ 0.7 kB empirical). Full catalog remains in git history.
12. Open Questions
- Real social proof (logos/quotes) once first orgs are live — placeholder until then, never fake it.
- v1 vs v2 landing — how do we call a winner? Both ship in parallel (§8, 2026-07-05) but the split is URL-only today, with no per-visitor bucketing and no conversion event wired. Decide the assignment mechanism (random bucket at
/, or referral-tagged links) and the metric (registrations attributed to the landing seen), run long enough for signal, then retire the loser and delete its component + i18n namespace. Until then neither version is “the” landing — they coexist.
13. Bug / Change Log
2026-07-31 — Official native downloads in both landing footers
Type: Distribution / Landing UX
Summary:
- Added restrained App Store and Google Play links to the current and classic localized landing footers through one shared component.
- Added the two official store-brand labels to every shipped
commonlocale file while preserving the trademark names unchanged. - Added red-first route proof for both landing variants and the exact production listing URLs.
Reason:
- The native apps were available publicly, but landing visitors had no direct download path from either footer.
2026-07-25 — Browser region locale no longer blocks registration
Type: Authentication / Localization Bug Fix
Summary:
- Canonicalize the anonymous browser locale through the shipped locale registry before sending
POST /api/auth/register/. - A detector value such as
en-USnow becomes the backend-supporteden; explicitly shipped variants such ases-MXremain unchanged. - Added a red-first auth-store regression and retained the browser onboarding journey as runtime proof of the complete registration-to-organization path.
Reason:
- The browser language detector can retain a regional code even when i18next resolves its
resources through a supported base language. Sending the uncanonicalized detector value made
Django reject the
languagefield and left the user on registration.
2026-07-19 — Brief-owned hero preview parity
Type: Product truth / deterministic proof
Summary:
- Replaced the landing-local hardcoded preview array with a typed model whose two open items use the
shipped Brief section vocabulary and whose completed deposit entry uses the registered
mark_deposit_paidassistant action type. - Removed the preview’s “Approve all” link because no shipped Brief bulk-approval operation exists; the real registration CTA remains unchanged.
- Added
F22.AC001.PREVIEW_PARITY, backendF22.AC001.PREVIEW_ACTION, and route assertions for the three localized entries and the absence of the false affordance. This closes only preview drift; no browser, visual, conversion, native, live-provider, Redis, Sentry, or production evidence is inferred.
2026-07-15 — Progressive-static default landing and enforced document budget
Type: Performance architecture / deploy artifact
Summary:
- Keep Playwright prerender as the HTML source of truth, then remove all workspace module scripts and modulepreloads from each default localized landing document.
- Ship one content-hashed, same-origin enhancement module for the existing non-secret session-hint redirect and supported-locale full navigation; native links and
<details>require no JavaScript. - Preload only the active locale’s critical Geist weights. The first implementation reached 0.7 kB JS but regressed CLS to 0.133; font-swap attribution identified the cause, and locale-aware preloads restored CLS to 0.000.
- Extend the bundle checker with deployed-document custody and make
build:ssgenforce the unchanged 150 kB landing target. Final proof: 0.5 kB gzip by artifact graph, 0.7 kB empirical transfer, floor LCP 1176 ms, FCP 1160 ms, CLS 0.000. - Registered CTA/auth and phone/tablet Chromium journeys pass. This closes the obsolete browser-runtime gap without claiming anonymous cross-device locale handoff or visual sign-off.
2026-07-15 — Production prerender custody and CWV artifact parity
Type: Performance correctness / evidence custody
Summary:
- Sanitize each Playwright prerender snapshot through a structured HTML parser so runtime-injected preview-origin modulepreloads and the already-executed GTM child script are not serialized into the deploy document.
- Keep the source modulepreloads, inline GTM bootstrap, metadata, and prerendered root markup intact; when no GTM ID is configured, the inline bootstrap performs no network request.
- Make the P0 CWV runner reject a Landing selection unless
dist/store/en/index.htmlexists, sopnpm buildcan no longer masquerade as the productionpnpm build:ssgartifact. - Red-first unit and production-browser tests pass. A full 224-route prerender completed with zero failures. The real SSG artifact fell from 17 to 8 modulepreloads, from 9 to 0 preview-origin references, and from 412.5 to 226.8 kB floor JS transfer. CLS remains 0.000; LCP is unchanged within noise (3104→3116 ms), so W-SYS-01 remains below target.
2026-07-14 — Contract Graph migration and honest browser boundary
Type: Contract / evidence audit
Summary:
- Added the canonical
contracts/features/F22.cuecontract for the localized route, registration CTA, authenticated redirect, and phone/tablet responsive journeys. - Registered proof IDs in the real Vitest and Playwright test titles so collection cannot be mistaken for execution.
- Added the implementation audit and test plan; anonymous locale handoff, hero-preview parity, browser runtime, and visual approval remain explicit blocking gaps.
Verification:
- Route/unit proof is executable locally.
- Browser proof is declared but not claimed green until the web server and Chromium run on the merge commit.
2026-07-13 — Hero image preload + body background fix (LCP improvement)
Type: Performance (CWV)
Summary:
- Added
<link rel="preload" as="image" href="/hero-hand.webp" fetchpriority="high" type="image/webp">tofrontend/index.html<head>. The browser now starts fetching the 44 KB hero image the moment it receives the HTML document — parallel with all JS/CSS loading — instead of waiting for React to render and discover the<img src>. This breaks the SPA discovery chain (HTML → JS parse → React render → image URL discovered) that held LCP at ~4.8 s even after the 2026-07-10 i18n fix. - Replaced
<body class="bg-gray-50 dark:bg-gray-900">with<body style="background-color: #F4F0E8">. The hex matches--paper-white(Lumi’s warm paper background, DESIGN.md §5). Dark mode is not applicable (light-only app;.darkclass never applied). The old body classes were banned stock Tailwind palette (Rule 4). - Verified: design-token check clean, locale parity clean, TypeScript 0 errors.
- CWV impact: documented in
docs/tests/evidence/web/W-STORE-01/cwv.md(follow-up pass #3). - POLISH.md F22 Performance remains 5 until the deployed change is measured; the audit records the expected mechanism without claiming an unobserved score gain.
2026-07-05 — Premium depth pass on v2 landing (display voice, lit sheets, motion)
Type: Visual Polish (UI/UX · §6)
Summary:
- Display voice. All section h2s, the hero h1, the scene punch line, and the final CTA now use
font-display(IBM Plex Sans) at tighter tracking, replacing the previous Inter-everywhere treatment. Step numbers become large ghosted editorial numerals. - Lit-sheet depth. Rule cards, the scene reply block, and the how-it-works grid use the
shadow-card/shadow-litdesign-token ladder; borders softened toborder-border/60–70; card radii refined to 14px. - Signature motion. Hero brief cards assemble in sequence on load (
hero-linestagger, 440–740ms delays).prefers-reduced-motioncollapses to static (handled by the existing.hero-lineCSS rule in index.css). - Rhythm. Section padding lifted from
py-16 md:py-24topy-20 md:py-28(problem/final sections topy-24 md:py-32) for art-gallery air. - Material lighting. Two
aria-hidden pointer-events-noneradial-gradient overlays on hero and final-CTA sections model room light without tinting text or adding dark-mode risk (app is light-only per DESIGN.md §5). - Problem §3 restructured. The
problem.p4answer beat is pulled out below aborder-trule at a larger editorial weight (text-[19px] md:text-[23px]); the remaining paragraphs given slightly more line-height (leading-[1.72]). - All changes inside
frontend/src/pages/store/LandingPage.tsx. No new i18n keys added; no locale files touched; existinglandingV2namespace unchanged. tsc --noEmit0 errors · design-token guard clean (node scripts/check-design-tokens.mjs) ·pnpm buildok.
2026-07-05 — Two landings in parallel for A/B comparison: v2 default from the copy master, v1 preserved at /store/$lang/v1
Type: Feature Change (UI/UX · §6, §7, §8)
Intent: keep both the original and the new landing live at the same time so we can see which one converts before committing — not a replacement, a comparison. See §8 “Two landing versions live in parallel” for the routing/namespace map and the still-open decision to add per-visitor bucketing + a conversion metric before picking a winner.
Summary:
- Shipped a new v2 landing at the default route
/store/$lang(and/→/store/$lang), built line-for-line from the copy master docs/marketing/landingpage.md: scene-before-claims order, the one real Vietnamese voice-note exchange, the four-step “how it works”, the six “rules Lumi can’t break” trust section, the compounding/built-for-here two-up, who-it’s-for, an FAQ, and a “Bring the mess” final call. New i18n namespacelandingV2, fully localized across all 55 non-English locales (en source; vi/ms/id hand-authored; the remaining 52 viadeepseek-v4-flashthrough the OpenAI-compatible endpoint configured in the user’s opencodesuperupprovider — opencode’s own headlessrunhung in this environment, so the model was called directly, key read from config at runtime, never hardcoded). The sixen-*regional variants correctly stay English. Brand names (Lumi,Lumi AI Workspace) and the pinned Vietnamese source phrases are preserved verbatim in every locale; F18 key-parity (10,147 assertions) and the production build both pass. - Preserved the previous v1 data-first landing verbatim at
/store/$lang/v1(LandingPageV1.tsx,landingnamespace, self-referential nav/footer). Registered/store/$lang/v1inroutes/routeTree.tsand inpublicRoutesRegistered.test.tsso it can’t fall through to the authed catch-all. A discreet “Classic page” footer link on v2 points to it. - Updated the landing CTA E2E (
describe("Landing")inlumi.spec.ts) to match v2’s Vietnamese register-bound CTAs (“Dùng thử Lumi” / “Thử bằng chính mớ hỗn độn của bạn”).
Positioning note (for review): landingpage.md leads with “Run your company by talking to it.” and the “AI chief of staff” framing — aligned with the CLAUDE.md Assistant-First Doctrine (F91), but in tension with this spec’s §1 and strategy-v2 §14.1, which banned “AI Chief of Staff” as an external phrase for the Vietnam vertical and mandated the F70 Zalo/Excel wedge. Shipped per explicit direction to adopt the newer copy master; v1 (which still embodies the F70 wedge) remains available. Flagged here rather than silently overriding — strategy-v2 §14.1 should be reconciled with landingpage.md as the marketing source of truth.
2026-07-02 — Correct stale landing auth-flash audit note
Type: Documentation Change
Summary:
- Updated §4, §5, §8, §9, and §11 to reflect the shipped localized landing flow:
/redirects to/store/:lang, then authenticated users are redirected from the localized landing before marketing content renders. - Reclassified the old auth-flash warning as stale spec drift rather than a live UX issue.
- Left the two real landing gaps intact: anonymous language preference still does not persist to backend profile state, and the hero preview remains hardcoded mock data.
Reason:
- The current frontend reads
isAuthenticatedsynchronously from the stored access token inuseAuthStore, soLandingPagereturns<Navigate to="/work/today" replace />immediately instead of waiting for post-hydration auth state.
Impact:
- Documentation only. No frontend behavior changed.
Tests:
- None (documentation change).
2026-06-18 — Visual QC pass 1: F70 wedge hero shipped
Type: UX / Positioning
Summary:
- Reworked the public landing hero from the older generic daily-update promise to the F70 wedge: paste messy Zalo/Excel/site updates, receive the owner’s morning brief.
- Replaced the desktop hero’s remote editorial photos with a static in-product owner brief preview so the first viewport shows the actual output Lumi sells.
- Reduced first-viewport empty space so the landing page signals the next section instead of presenting a mostly blank right side.
- Updated
landinglocale keys in English and Vietnamese together.
Verification:
frontend/tests/e2e/screenshot-flow.spec.tscaptures01-landing/01-home.png,scroll-mid, andscroll-bottom.- Landing E2E and i18n parity remain the gates for regression.
2026-06-15 — Frontend route namespaced
Frontend route namespaced: authenticated redirect target /today -> /work/today (app-wide IA restructure; API paths unchanged).
2026-06-14 — Strategy v2 realignment (positioning)
Type: Documentation Change
Summary:
- Reframed §1 Problem & CEO Job, §2 hero acceptance line, §3 scope, from the generic “morning-AI promise” (“Lumi đọc tình hình công ty cho bạn mỗi sáng”) to the F70 wedge for fit-out / renovation / small M&E contractor owners: paste the Zalo/Excel mess → today’s owner brief (ai đang nợ việc, khách nào cần chốt, việc nào trễ), using the strategy-v2 §14.2 messaging formula.
- Added §11 audit row 9: the implemented hero copy is not yet reframed — recorded as a gap, not claimed shipped. Summary updated to 5/9 match, 4 gaps.
Reason: competitive-strategy-v2.md §14.1 (banned external phrases: “AI Chief of Staff”, “all-in-one AI workspace”, “Notion + Jira + Slack + AI”) and §14.2 (messaging formula), plus the MVP_SCOPE.md “Positioning doctrine (strategy v2, 2026-06-14)” — competitor is Zalo/WhatsApp + Excel + owner memory, not US SaaS; wedge = F70.
Impact: Positioning prose only. No data model, API, route, test, or status change. The actual LandingPage.tsx hero copy change is logged as a §11 gap (action: rewrite hero copy to wedge), not claimed done.
Tests: none (documentation).
Notes: External marketing must not lead with “AI Workspace” (banned per §14.1); strategy v2 §18 permits “AI Workspace” only as an internal architecture label.
2026-06-14 — Restructure to TEMPLATE.md section order; expand §11 audit (SP fleet)
Type: Documentation Change
Summary:
- Numbered “Current Behavior” as §8.
- §8 Diagrams → §10; §9 Implementation Audit (verdict) → §11 (8-row table); §10 Open Questions → §12; §11 Edge Cases absorbed into §11 note; §12 Error States → §9; §13 Bug Log stays.
- §11 audit: 8 rows, 5 ✅, 3 gaps (anonymous language persistence, mock data drift, auth flash).
2026-06-12 — Add Edge Cases (E1–E10) and Error States (ES1–ES6) to F22 spec
Type: Documentation Change
2026-06-11 — Add functional landing E2E
Type: Feature
Summary:
- Added
describe("Landing")covering CTA →/registerand authenticated/→/today. - Screenshot-only coverage was too weak for the page whose whole job is to convert.