44 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, assumption_delta_decision, specless_probe_fallback, user_setup, estimate, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | assumption_delta_decision | specless_probe_fallback | user_setup | estimate | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin | 01 | execute | 1 |
|
true | no-change | skipped: phase has no requirement IDs to probe (visible skip); ROADMAP SC1-SC5 and the D-IDs are the acceptance contract |
|
|
Purpose: SC1. The Go binary in plan 11.2-02 embeds this output, so the generate output contract here (file layout, _nuxt/, _fonts/, _i18n/, flat sitemap.xml) is what the site handler serves.
Output: a new git repository at /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app (it becomes a submodule of sm-summercmsio-app in plan 11.2-02), with node:test assertions over the generated output and the two pure utilities.
<execution_context>
@/.claude/gsd-core/workflows/execute-plan.md
@/.claude/gsd-core/templates/summary.md
</execution_context>
Paths. Commands run from the summercms.go root. SITE = ../sm-summercmsio-app/vue-summercmsio-app (absolute: /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app). Every file in this plan is written to the SITE repository, which this plan creates. Nothing in summercms.go changes in this plan. Do not run git add in the meta repository summercms/ or in summercms.go; sm-summercmsio-app/ stays a plain directory until plan 11.2-02 makes it a repository.
Commits. Commit inside SITE only, one logical change per commit, conventional messages such as feat(site): ..., and never a co-author tag.
Design source of truth. design/SummerCMS Landing.dc.html is authoritative for punctuation and details not in design/README.md (RESEARCH "Details found in SummerCMS Landing.dc.html"): typographic apostrophes in "each plugin’s YAML" and "the caller’s transaction", the straight apostrophe in "WHAT'S IN THE BOX", gaps, paddings and the terminal layout. Ignore its support.js runtime and its JS width state.
app/utils/terminal.ts:export interface TerminalGroup { comment: string; commands: string[] }andexport function copyPayload(groups: readonly TerminalGroup[]): stringreturning every command in order joined by\n, with no trailing newline, no$and no comment text.app/utils/scrollSpy.ts:export const SPY_SECTIONS=['why', 'features', 'winter', 'start'],export const SPY_THRESHOLD = 140, andexport function activeSection(tops: ReadonlyArray<{ id: string; top: number }>, threshold?: number): stringreturning the id of the last entry whosetopis strictly less than the threshold, or''when none is.app/data/terminal.json:{ "groups": [ { "comment": "<i18n key>", "commands": [ ... ] } ] }with exactly these groups in order:terminal.getFramework→git clone https://git.golem15.com/golem15/summercms,cd summercms;terminal.installCli→go install ./cmd/summer;terminal.buildExample→cd examples/hello,summer build,./bin/hello greeter:hello. Plan 11.2-02 reads this file from Go (D-40 check and the HTML drift guard), so the key names and shape are a contract.app/app.config.ts:defineAppConfig({ landing: { showRays: true, scrollSpy: true } }).
- Repository (per D-01 and D-43):
mkdir -p /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app, thengit init -b masterinsidevue-summercmsio-apponly. Add.gitignore(node_modules/,.nuxt/,.output/,.data/,dist,.env),.nvmrcwith22.22.0, and a shortREADME.md(what the site is,pnpm install --frozen-lockfile,pnpm generate,pnpm test,scripts/derive-images.sh, and that the root app embeds.output/public). - Packages (T-11.2-SC):
package.jsonwith"name": "vue-summercmsio-app","private": true,"type": "module","engines": { "node": ">=22.6" }, scriptsbuild(nuxt build),dev(nuxt dev),generate(nuxt generate),preview(nuxt preview),prepare(nuxt prepare),test(node --test tests/*.test.ts), and no install-time lifecycle script. Install with exact pins viapnpm add --save-exact nuxt@4.4.8 @nuxt/fonts@0.14.0 @nuxtjs/i18n@10.4.0 @nuxtjs/seo@5.2.1 vue@3.5.35andpnpm add -D --save-exact typescript@5.9.3. These are the versions running in vue-fonoteka-app and approved in RESEARCH "Package Legitimacy Audit". If pnpm cannot resolve any of these exact versions, stop and return acheckpoint:human-verifywithgate="blocking-human"naming the package and version instead of installing another version. Copy fonoteka'spnpm-workspace.yamlallowBuildsentries for@parcel/watcher,esbuildandvue-demionly (no protobufjs, no sharp, no patchedDependencies). Commitpnpm-lock.yaml. Addtsconfig.jsonextending./.nuxt/tsconfig.jsonas fonoteka does. nuxt.config.ts(the spike-verified config in RESEARCH Pattern 1, per D-04, D-33, D-34, D-37):compatibilityDate: '2025-07-15',devtools: { enabled: false },modules: ['@nuxt/fonts', '@nuxtjs/i18n', '@nuxtjs/seo'],cssloading~/assets/css/tokens.cssthen~/assets/css/base.css;fonts.familiesRoboto weights 300/400/500/700 and Roboto Mono weights 400/500, each withstyles: ['normal'],subsets: ['latin', 'latin-ext'],global: true(Roboto 900 from the prototype is not loaded, per D-34);i18nwithstrategy: 'prefix_except_default',defaultLocale: 'en', one locale{ code: 'en', language: 'en-US', file: 'en.json', name: 'English' },langDir: 'locales/',baseUrl: 'https://summercms.io',experimental: { prerenderMessages: true };site: { url: 'https://summercms.io', name: 'SummerCMS', description: <the hero description>, defaultLocale: 'en' };ogImage: { enabled: false };sitemap: { autoI18n: false }(Pitfall 2: one flat file);linkChecker: { excludeLinks: ['/docs', '/docs/**'] };nitro: { prerender: { ignore: ['/docs'] } }(Pitfall 1: generate otherwise exits 1 on /docs links);app.head.linkwith/favicon.ico,/apple-touch-icon.png(sizes 180x180).app/app.config.tsper D-45 (both flagstrue).app/app.vuerenders<NuxtPage />.app/pages/index.vuerendersSiteHeaderandHeroSectioninside a wrapper whose horizontal overflow is clipped withoverflow-x: clip, neverhidden(Pitfall 3: a hidden-overflow ancestor breaks the sticky header), and sets the page head withuseSeoMeta: titleSummerCMS: A new dawn in content managementwith atitleTemplatethat does not append the site name again (spike finding 4),description= the hero description,ogImage/og-image.pngwith width 1200, height 630 and alt text,twitterCard: 'summary_large_image'. Title, description and alt live inen.jsonundermeta.*.i18n/locales/en.json(D-33): nested keys formeta,header(logo aria label, wordmark parts "Summer" and "CMS", pillALPHA 0.1, nav labels Why, Features, From WinterCMS, Get started,Docs →),hero(tagline "A new dawn in content management", description "A content management framework for Go, inspired by WinterCMS. Plugins, the admin SPA and console commands compile into a single binary.", CTA labels "Read the docs" and "Alpha 0.1 is out"). Copy every string verbatim from the handoff. Use no vue-i18n special characters (@,|,{,}) in values except deliberate slot placeholders (Pitfall 6).- Styles per D-35:
tokens.cssdefines on:rootthe fourteen color tokens from RESEARCH "Tokens → :root custom properties" (--navy-900#18223athrough--text-4#8a94a3), white border alphas 6/7/8/10/12/14% as--border-06…--border-14, radii--radius-sm6px,--radius-card16px,--radius-pill999px, and font stacks--font-sans(Roboto, system-ui, sans-serif) and--font-mono(Roboto Mono, ui-monospace, monospace).base.cssholds the globals from design/README.md "Global": page background#233148, text#e9edf3,html { scroll-behavior: smooth; scroll-padding-top: 72px }plus@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto } }(discretion choice), selection colors, link colors and hover,.container(max-width 1120px, padding 0 24px, auto margins), the shared section heading pattern (.eyebrow,.section-title), and the shared button shapes (primary, secondary, outline) and chip shape used by later sections. Component styles are<style scoped>and reference the tokens. No CSS framework package. SiteHeader.vueper design/README.md "1. Header" and the dc.html details: sticky 64px bar, translucent navy with blur, logo link to#top(30px sun image/sun-logo.webpsized to match the handoff visually, wordmark "Summer" white plus "CMS"#fbd77e), theALPHA 0.1pill, the section links in aflex: 1; min-width: 0; overflow-x: auto; scrollbar-width: nonewrapper with the inner row pushed right, and theDocs →button linking/docswith the arrow in its own span. Section links are always rendered and hidden by@media (max-width: 720px)(Pitfall 4). Give each nav link adata-sectionattribute; active styling is wired in Task 3.HeroSection.vueper "2. Hero":<section id="top">, the rays layer rendered only whenuseAppConfig().landing.showRaysis true (D-45), the glow, the 180px sun badge holding/sun-badge.webpat about 136px (RESEARCH Open Question 5), the H1 with the gradient "CMS", tagline, description, and the two CTAs (/docsand#start, the secondary with its 8px dot).- Images per D-36 with
scripts/derive-images.sh(bash,set -euo pipefail, ImageMagickmagickonly, run from the SITE root): download the original once withcurl -fsSL https://summercms.io/logo.png -o assets-src/logo.pngand commit it (the live URL disappears at cutover), check it is 746x744 (magick identify), then writepublic/sun-logo.webp(60x60, 2x of 30px),public/sun-badge.webp(272x272, 2x of 136px),public/favicon.ico(16, 32 and 48 px layers),public/apple-touch-icon.png(180x180 on#233148), andpublic/og-image.png(1200x630 on#233148with the sun, the wordmark "Summer" white and "CMS"#fbd77e, and the tagline in#c5ccd6, set in Roboto from/usr/share/fonts/TTF/). Commit the script and every output. Do not copydesign/assets/sun-crop.png. tests/output.test.ts(node:test; every test file resolves paths fromimport.meta.url, never from the working directory, so it runs from any cwd; reads files under.output/publicand fails with a clear message whenindex.htmlis missing):index.htmlcontains<section id="top", the hero tagline and description,href="/docs", a<link rel="canonical" href="https://summercms.io/",og:imagewith contenthttps://summercms.io/og-image.png,twitter:cardsummary_large_image,<html lang="enand a JSON-LD@graph;_fonts/holds at least one.woff2;robots.txtcontainsSitemap: https://summercms.io/sitemap.xml;sitemap.xmlis a regular file containing<loc>https://summercms.io/</loc>; at least one_i18n/**/en/messages.jsonexists (Pitfall 7 contract for plan 11.2-02); no generated.htmlor.cssfile mentions either Google font host named in the acceptance criteria. Build the en.json leaf check as a helper reused by later tasks: every leaf string without a{placeholder, HTML-escaped the way Vue escapes text (&,<,>,",'), appears inindex.html.
Commit as feat(site): scaffold the Nuxt 4 landing page with header, hero, fonts and SEO.
pnpm -C ../sm-summercmsio-app/vue-summercmsio-app install --frozen-lockfile && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test
<fails_when>non-zero exit, "Exiting due to prerender errors" in the generate log, a "not ok" line in the node:test TAP output, or "# fail" with a count above 0</fails_when>
<acceptance_criteria>
- node -p "const p=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/package.json'); [p.dependencies.nuxt,p.dependencies['@nuxt/fonts'],p.dependencies['@nuxtjs/i18n'],p.dependencies['@nuxtjs/seo'],p.dependencies.vue,p.devDependencies.typescript].join(' ')" prints 4.4.8 0.14.0 10.4.0 5.2.1 3.5.35 5.9.3.
- test -f ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html && test -f ../sm-summercmsio-app/vue-summercmsio-app/.output/public/sitemap.xml succeeds (a regular file, not a directory).
- ls ../sm-summercmsio-app/vue-summercmsio-app/.output/public/_fonts/*.woff2 | wc -l prints at least 1.
- grep -c "fonts.googleapis.com\|fonts.gstatic.com" ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints 0.
- grep -n "ignore: \['/docs'\]" ../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts finds a match.
- magick identify ../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png reports 1200x630, and magick identify ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png reports 746x744.
- git -C ../sm-summercmsio-app/vue-summercmsio-app log --format='%(trailers:key=Co-authored-by,valueonly)' master prints only empty lines.
</acceptance_criteria>
A new vue-summercmsio-app repository generates a prerendered page with the handoff header and hero from en.json, self-hosted Roboto fonts, the original sun in every derived image, static SEO tags, flat sitemap and robots, and its output test passes.
WhySection.vue(<section id="why">): eyebrowWHY SUMMER, H2 "What you tested is exactly what you deploy." (max-width 720px), and the three cards (number in Roboto Mono, title, body) on arepeat(auto-fit, minmax(260px, 1fr))grid. Card 2's body has inline code forfields.yamlandcolumns.yaml(Roboto Mono 13px,#e9edf3): render it with the<i18n-t>component and named slots for the two file names (Pitfall 6). Do not use Vue's raw-HTML binding directive anywhere in the project (T-11.2-01).FeaturesSection.vue(<section id="features">, full-bleed#1f2b42band with top and bottom borders): eyebrowWHAT'S IN THE BOX, H2 "The services a content app needs.", and the eight tiles as links on the 1px-divider grid (gap: 1pxover the border color, outer border, radius 16px,overflow: hidden). Tile hrefs per D-47, exactly as the handoff writes them, extension-less:/docs/backend/admin-spa,/docs/database/models,/docs/services/routing,/docs/services/jobs,/docs/services/realtime,/docs/services/search,/docs/services/mail,/docs/console/introduction. Keep tile titles and bodies verbatim, including "the caller’s transaction" with its typographic apostrophe. Keep the hrefs in the component (or a small typed array next to it) and the copy in en.json.WinterSection.vue(<section id="winter">), per D-09 with no added seasons copy: two columnsrepeat(auto-fit, minmax(300px, 1fr)), gap 48px; left column eyebrowCOMING FROM WINTERCMS, H2 "Same shape. Compiled and typed.", the paragraph, and "See the full concept map →" linking/docs/setup/coming-from-wintercms(display: inline-flex; margin-top: 24px); right column the concept table with headerWINTERCMS | SUMMERCMSand the seven rows from design/README.md (minmax(0,1fr) minmax(0,1.3fr)columns, Roboto Mono 13px, every row including the last with a bottom divider).SiteFooter.vue: top border, 32px 24px padding, 13px#8a94a3; left "© 2026 SummerCMS. Something bright is here."; aflex: 1spacer; the link group (gap 20px,#a3adbd, hover#f5c55a) with Docs →/docs, Source →https://git.golem15.com/golem15/summercms(D-38), the D-12 credit "Built by Golem15" →https://golem15.comstyled as a footer link, and "Back to top ↑" →#top.index.vuerenders header, hero, Why, Features, Winter, then the footer (the Get started section is added in Task 3 between Winter and the footer).- Extend
tests/output.test.ts: section idswhy,features,winterpresent aftertop; the eight tile hrefs plus/docs/setup/coming-from-wintercmseach appear ashref="…"; the Source href andhttps://golem15.comappear; the en.json leaf check passes for the new keys; the seven concept rows (both columns) appear.
Commit as feat(site): add the Why, Features and From WinterCMS sections and the footer.
pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test
<fails_when>non-zero exit, "Exiting due to prerender errors" in the generate log, or a "not ok" line in the TAP output</fails_when>
<acceptance_criteria>
- grep -o 'href="/docs/[a-z/-]*"' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html | sort -u | wc -l prints at least 9 (eight tiles plus the concept map).
- grep -c 'href="https://golem15.com' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints at least 1.
- grep -c 'caller’s transaction' ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json prints 1.
- grep -rn 'v-html' ../sm-summercmsio-app/vue-summercmsio-app/app prints nothing.
- grep -c '<i18n-t' ../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue prints at least 1.
</acceptance_criteria>
The Why, Features and From WinterCMS sections and the footer render from en.json with the handoff's links (extension-less docs hrefs, Source, Golem15 credit), and the output test covers them.
app/data/terminal.jsonexactly as the interfaces block defines it (D-39: the clone step comes first; D-40: this file is the single source the page renders and plan 11.2-02's Go check reads). The comment keys resolve inen.jsontoget the framework,install the summer CLIandbuild and run the example app; the component prints them after#.app/utils/terminal.tsandapp/utils/scrollSpy.tsexactly as the interfaces block defines them: pure functions, no Vue or Nuxt imports, erasable TypeScript only, sonode --testimports them directly with explicit.tsspecifiers.TerminalCard.vueper "6. Get started → terminal card": title bar (terminallabel, Copy button 500 12px Roboto, radius 6px), body padding 22px 20px, Roboto Mono 14px, line-height 1.9,overflow-x: auto, flex column; each line is its own block withwhite-space: pre; comment lines#8a94a3; the$prompt#f5c55ain its own span so the command text stays contiguous in the HTML; each group's comment after the first hasmargin-top: 16px. Copy callsnavigator.clipboard?.writeText(copyPayload(groups))and falls back to a hidden textarea plusdocument.execCommand('copy')when the Clipboard API is unavailable (Pitfall 5); on success the label shows the en.jsonCopiedtext for 1500 ms, thenCopy; a failed copy leaves the label unchanged.StartSection.vue(<section id="start">, full-bleed#1f2b42band with a top border): two columns as in the Winter section withalign-items: center; left column eyebrowGET STARTED, H2 "Build your first app.", the chips row (margin-top: 24px; gap: 8px) withGo 1.27andPostgreSQL 15+(D-26, following D-25; plan 11.2-02 re-runs the PG 15 suites and stops if they fail), the note "Alpha 0.1 is for early adopters. APIs may change before 1.0." (margin-top: 20px, 16px/1.65), and the buttons row (margin-top: 28px): "Installation guide" primary →/docs/setup/installation(padding 13px 24px; extension-less per D-47) and "Source" outline →https://git.golem15.com/golem15/summercms(D-38; padding 12px 22px, border 14% white, hover background 8% white and text#fff). Right column the TerminalCard. Insert it inindex.vuebetween Winter and the footer.- Scroll-spy in
SiteHeader.vue(D-45): whenuseAppConfig().landing.scrollSpyis true,onMountedcomputes the active id fromgetBoundingClientRect().topof the four sections viaactiveSection, then on everyscrollevent with{ passive: true };onBeforeUnmountremoves the listener. The active link gets#f5c55atext andrgba(245,197,90,0.1)background. SSR renders no active link (Pitfall 4). With the flag false no listener is attached. - Extend
tests/output.test.tswith the last behavior bullet (it reads the six commands and three comment keys fromterminal.jsonanden.json, never from a second hard-coded list) and with the order checktop<why<features<winter<start< footer.
Commit the tests and the implementation as feat(site): add the Get started terminal card, copy button and scroll-spy.
pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test
<fails_when>non-zero exit, a "not ok" line in the TAP output, or the TAP summary missing the terminal and scrollSpy test files</fails_when>
node --test ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts
<fails_when>non-zero exit, "# pass 0", or "# fail" with a count above 0</fails_when>
Run pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run preview and open the printed URL next to design/SummerCMS Landing.dc.html in a browser. At 1280px, 721px, 720px and 375px wide: layout, colors, type and spacing match; the header stays stuck while scrolling; section links are visible at 721px and hidden at 720px; the active link follows the section in view and none is active above Why; Copy changes to "Copied" for about 1.5s and the clipboard holds six lines without $ or comments; the hero rays and the sun badge match the handoff.
<acceptance_criteria>
- node -e "const t=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json'); console.log(t.groups.length, t.groups.flatMap(g=>g.commands).length)" prints 3 6.
- grep -c 'PostgreSQL 15+' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints at least 1.
- grep -c './bin/hello greeter:hello' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html prints at least 1.
- grep -n "@media (max-width: 720px)" ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue finds a match.
- grep -n 'passive: true' ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue finds a match.
- grep -n 'scrollSpy' ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue and grep -n 'showRays' ../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue both find a match.
</acceptance_criteria>
The Get started section shows the D-26 chips, the D-38 links and the D-39 terminal card fed from terminal.json; Copy writes the six commands; scroll-spy follows the handoff rule behind the D-45 flag; unit and output tests pass.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| npm registry → build machine | Third-party packages execute during install and generate |
| en.json / terminal.json → rendered HTML | Repository copy becomes public HTML; authors are trusted, mistakes are expected |
| page → visitor clipboard | The Copy button writes text the visitor will paste into a shell |
| page → third-party origins | Any runtime request to another origin leaks visitor data |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-11.2-01 | Tampering (XSS) | components rendering en.json | medium | mitigate | Text interpolation only; inline code through <i18n-t> slots; Task 2 criterion greps that no raw-HTML binding exists under app/ |
| T-11.2-02 | Information disclosure (privacy) | font loading | medium | mitigate | @nuxt/fonts downloads at build and serves from /_fonts/ (D-34); the output test and a criterion assert no Google font host in the generated HTML or CSS |
| T-11.2-03 | Tampering | Copy button payload | low | accept | Payload is the bundled terminal.json, identical to the visible lines; the page only writes to the clipboard and never reads it |
| T-11.2-SC | Tampering | pnpm installs | high | mitigate | Exact pins equal to vue-fonoteka-app's installed versions, approved in RESEARCH Package Legitimacy Audit; committed pnpm-lock.yaml and --frozen-lockfile; allowBuilds limited to three packages; any other version stops at a blocking-human checkpoint |
| </threat_model> |
<success_criteria>
- SC1:
nuxt generateproduces the landing page matching the handoff with sticky header and scroll-spy, hero, Why, Features, From WinterCMS, Get started, a working Copy button and the footer; responsive to phone width with section links hidden at 720px or less (visual parts confirmed in UAT). - Inputs for SC2 and SC3: the
.output/publictree andapp/data/terminal.jsonthat plan 11.2-02 embeds, link-checks and runs. </success_criteria>
Artifacts this phase produces
- Repository
vue-summercmsio-appat/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app(branchmaster, no remote yet; D-43). - Config:
nuxt.config.ts(modules@nuxt/fonts,@nuxtjs/i18n,@nuxtjs/seo;nitro.prerender.ignore,sitemap.autoI18n: false,ogImage.enabled: false,linkChecker.excludeLinks,i18n.experimental.prerenderMessages),app/app.config.tskeyslanding.showRays,landing.scrollSpy,package.jsonscriptsbuild,dev,generate,preview,prepare,test. - Components:
SiteHeader,HeroSection,WhySection,FeaturesSection,WinterSection,StartSection,TerminalCard,SiteFooter; pageapp/pages/index.vue. - TypeScript:
TerminalGroup,copyPayload(app/utils/terminal.ts);SPY_SECTIONS,SPY_THRESHOLD,activeSection(app/utils/scrollSpy.ts). - Data and copy:
app/data/terminal.json(groups[].comment,groups[].commands),i18n/locales/en.json(keysmeta.*,header.*,hero.*,why.*,features.*,winter.*,start.*,terminal.*,footer.*). - Styles:
app/assets/css/tokens.css(--navy-900…--text-4,--border-06…--border-14,--radius-sm,--radius-card,--radius-pill,--font-sans,--font-mono),app/assets/css/base.css. - Images:
assets-src/logo.png,public/sun-logo.webp,public/sun-badge.webp,public/favicon.ico,public/apple-touch-icon.png,public/og-image.png; scriptscripts/derive-images.sh. - Tests:
tests/output.test.ts,tests/terminal.test.ts,tests/scrollSpy.test.ts.