shipped

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.
  • As a visitor, I want the page in my language — vi default for the market, en switchable (landing namespace); 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/:lang document 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:ssg artifact 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

#CaseTypeWhere
1Renders, CTA navigates to registere2edescribe("Landing")
2Authenticated / enters the workspace; authenticated direct landing redirects to /work/todaye2edescribe("Landing") + static runtime unit
3Visual at key widthse2escreenshots 01-landing/
4Locale parityuniti18n test (F18)
5Prerender sanitation removes preview replay artifacts and replaces the default landing workspace graph with one content-hashed enhancement runtime while preserving metadata and root markupunitsrc/test/store/prerenderHtml.test.ts
6Landing CWV artifact custody rejects a plain SPA build and accepts dist/store/en/index.htmlunit + harnesssrc/test/performance/cwvArtifactCustody.test.ts; scripts/measure-cwv-p0.mjs
7Built landing document executes only its enhancement runtime, retains native CTA/FAQ behavior, changes supported locale by full navigation, and contains no preview/workspace graph requestproduction-browsertests/e2e/prerender-runtime.spec.ts
8Enhancement preserves returning-session redirect and rejects mutated unsupported locale valuesunitsrc/test/store/landingRuntime.test.ts
9Locale-critical Geist subsets are preloaded without unrelated locale fontsunit + artifact browsersrc/test/store/prerenderHtml.test.ts; tests/e2e/prerender-runtime.spec.ts
10Actual deployed landing document passes the 150 kB JavaScript budgetunit + build gatesrc/test/_shared/bundle-budget.test.ts; build:ssg document gate
11Current and classic landing footers link to the exact official App Store and Google Play listingsunitF22.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:

VersionComponentRoute(s)Copy sourcei18n namespaceFraming
v2 (default)pages/store/LandingPage.tsx/ · /store/:langdocs/marketing/landingpage.mdlandingV2”Run your company by talking to it.” — assistant-first / chief-of-staff (F91)
v1 (archive)pages/store/LandingPageV1.tsx/store/:lang/v1(original)landingF70 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/v1 vs 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 landingV2 locales; v1’s landing namespace is frozen unless the archive itself is being corrected.

9. Error States

#ErrorResponseNotes
ES1CTA registration route is undefinedRouter not-found surfaceCTA route and browser journey are contract-tested
ES2LanguageSwitcher i18n-not-ready racei18n.changeLanguage throws if called before initNo test for i18n-not-ready scenario
ES3Lazy import of LandingPage fails in development/SPA fallbackTanStack Router error boundaryProduction default landing does not depend on this import; fallback failure proof remains absent
ES4Missing vi keys for new hero sectioni18next renders key name instead of vi textF18 parity test catches this only if both locales updated
ES5Auth redirect misfires despite a returning-session hint/cookieAuthenticated user lands on marketing instead of the workspaceRoot browser journey and static-runtime session-hint unit proof cover both entry boundaries
ES6Footer link broken404 on footer route (e.g., /privacy)Landing E2E only checks primary CTA and redirect
ES7Prerender snapshot replays runtime-injected assetsPreview-origin preloads fail in production and duplicate third-party scripts compete with the LCP imageSanitize the captured DOM before writing and fail a deterministic artifact test on forbidden URLs/scripts
ES8CWV run grades pnpm build instead of pnpm build:ssgRegistry reports an artifact Cloudflare never servesThe canonical runner exits before opening Chromium when the localized SSG document is absent
ES9Static enhancement accepts a mutated unsupported localeNavigation reaches a non-canonical store pathRuntime checks the selected value against the document’s supported options before navigating
ES10Geist swaps after first paintHero/nav reflow pushes CLS above 0.1Build 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)

#ClaimStatusEvidence
1Hero: one headline, one CTA → /register; no carousel/autoplay✅ matchLandingPage.tsx; TASTE.md compliance; E2E CTA test
2Logged-in / → redirect to /work/today; logged-out → full landing✅ match<Navigate to="/work/today" replace /> in LandingPage; describe("Landing") E2E
3LandingPage is React.lazy()-imported (code-split)✅ matchApp.tsx lazy import; bundle inspector
4vi default, en switchable via landing i18n namespace✅ matchF18 key parity test covers landing namespace
5Functional E2E: CTA → /register + authenticated redirect✅ matchAdded 2026-06-11; describe("Landing")
6Anonymous landing locale is not proven across devices⚠️ gapRegistration now sends the optional locale to User.language; the second-device browser journey and commit-bound runtime evidence remain absent
7Static hero preview remains semantically aligned with real Brief output✅ matchlandingBriefPreview.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
8Authenticated users with a stored token are redirected before landing content renders✅ matchuseAuthStore initializes isAuthenticated from localStorage synchronously; LandingPage returns <Navigate to="/work/today" replace />; / first routes through StoreLocaleRedirect but the localized landing still redirects immediately
9Hero copy reframed to the F70 wedge (paste Zalo/Excel mess → today’s owner brief) per strategy v2 §14.2✅ match2026-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.
10First viewport shows the product promise and a hint of the next section✅ match2026-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.
11Production SSG snapshot has no preview-origin or replayed analytics tags; CWV grades the SSG document✅ matchscripts/lib/prerender-html.mjs; scripts/lib/cwv-artifacts.mjs; focused Vitest + production Playwright proof; 2026-07-15 deployed-artifact measurement
12Production default landing does not execute the workspace graph and passes the actual-document JS budget✅ matchOne hashed enhancement module, no modulepreloads; document gate 0.5 kB gzip and empirical transfer 0.7 kB
13Overall visual quality has a maintained human-approved baseline⚠️ gapResponsive 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 common locale 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-US now becomes the backend-supported en; explicitly shipped variants such as es-MX remain 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 language field 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_paid assistant 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, backend F22.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:ssg enforce 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.html exists, so pnpm build can no longer masquerade as the production pnpm build:ssg artifact.
  • 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.cue contract 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"> to frontend/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; .dark class 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-lit design-token ladder; borders softened to border-border/60–70; card radii refined to 14px.
  • Signature motion. Hero brief cards assemble in sequence on load (hero-line stagger, 440–740ms delays). prefers-reduced-motion collapses to static (handled by the existing .hero-line CSS rule in index.css).
  • Rhythm. Section padding lifted from py-16 md:py-24 to py-20 md:py-28 (problem/final sections to py-24 md:py-32) for art-gallery air.
  • Material lighting. Two aria-hidden pointer-events-none radial-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.p4 answer beat is pulled out below a border-t rule 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; existing landingV2 namespace unchanged.
  • tsc --noEmit 0 errors · design-token guard clean (node scripts/check-design-tokens.mjs) · pnpm build ok.

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 namespace landingV2, fully localized across all 55 non-English locales (en source; vi/ms/id hand-authored; the remaining 52 via deepseek-v4-flash through the OpenAI-compatible endpoint configured in the user’s opencode superup provider — opencode’s own headless run hung in this environment, so the model was called directly, key read from config at runtime, never hardcoded). The six en-* 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, landing namespace, self-referential nav/footer). Registered /store/$lang/v1 in routes/routeTree.ts and in publicRoutesRegistered.test.ts so 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") in lumi.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 isAuthenticated synchronously from the stored access token in useAuthStore, so LandingPage returns <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 landing locale keys in English and Vietnamese together.

Verification:

  • frontend/tests/e2e/screenshot-flow.spec.ts captures 01-landing/01-home.png, scroll-mid, and scroll-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 → /register and authenticated / → /today.
  • Screenshot-only coverage was too weak for the page whose whole job is to convert.