[{"data":1,"prerenderedAt":736},["ShallowReactive",2],{"navigation_docs":3,"-tooling-screenshots":191,"-tooling-screenshots-surround":731},[4,31,67,103,129,155,175],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":30},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,25],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",{"title":26,"path":27,"stem":28,"icon":29},"Agent Skill","\u002Fgetting-started\u002Fagent-skill","1.getting-started\u002F4.agent-skill","i-lucide-sparkles",false,{"title":32,"icon":33,"path":34,"stem":35,"children":36,"page":30},"Authoring","i-lucide-pen-line","\u002Fauthoring","2.authoring",[37,42,47,52,57,62],{"title":38,"path":39,"stem":40,"icon":41},"Comark syntax","\u002Fauthoring\u002Fcomark-syntax","2.authoring\u002F1.comark-syntax","i-lucide-file-code",{"title":43,"path":44,"stem":45,"icon":46},"Frontmatter","\u002Fauthoring\u002Ffrontmatter","2.authoring\u002F2.frontmatter","i-lucide-heading",{"title":48,"path":49,"stem":50,"icon":51},"Modules","\u002Fauthoring\u002Fmodules","2.authoring\u002F3.modules","i-lucide-blocks",{"title":53,"path":54,"stem":55,"icon":56},"Merge tags and bindings","\u002Fauthoring\u002Fmerge-tags-and-bindings","2.authoring\u002F4.merge-tags-and-bindings","i-lucide-braces",{"title":58,"path":59,"stem":60,"icon":61},"Actions and spans","\u002Fauthoring\u002Factions-and-spans","2.authoring\u002F5.actions-and-spans","i-lucide-mouse-pointer-click",{"title":63,"path":64,"stem":65,"icon":66},"Compile errors","\u002Fauthoring\u002Fcompile-errors","2.authoring\u002F6.compile-errors","i-lucide-circle-alert",{"title":68,"icon":69,"path":70,"stem":71,"children":72,"page":30},"Rendering","i-lucide-layout-template","\u002Frendering","3.rendering",[73,78,83,88,93,98],{"title":74,"path":75,"stem":76,"icon":77},"Families and streams","\u002Frendering\u002Ffamilies-and-streams","3.rendering\u002F1.families-and-streams","i-lucide-users",{"title":79,"path":80,"stem":81,"icon":82},"Themes","\u002Frendering\u002Fthemes","3.rendering\u002F2.themes","i-lucide-palette",{"title":84,"path":85,"stem":86,"icon":87},"Config and the renderer","\u002Frendering\u002Fconfig-and-renderer","3.rendering\u002F3.config-and-renderer","i-lucide-settings",{"title":89,"path":90,"stem":91,"icon":92},"Layout and spacing","\u002Frendering\u002Flayout-and-spacing","3.rendering\u002F4.layout-and-spacing","i-lucide-ruler",{"title":94,"path":95,"stem":96,"icon":97},"HTML output","\u002Frendering\u002Fhtml-output","3.rendering\u002F5.html-output","i-lucide-code-xml",{"title":99,"path":100,"stem":101,"icon":102},"Plain text","\u002Frendering\u002Fplain-text","3.rendering\u002F6.plain-text","i-lucide-file-text",{"title":104,"icon":105,"path":106,"stem":107,"children":108,"page":30},"Sending","i-lucide-send","\u002Fsending","4.sending",[109,114,119,124],{"title":110,"path":111,"stem":112,"icon":113},"Message and MIME","\u002Fsending\u002Fmessage-and-mime","4.sending\u002F1.message-and-mime","i-lucide-mail",{"title":115,"path":116,"stem":117,"icon":118},"Router and streams","\u002Fsending\u002Frouter-and-streams","4.sending\u002F2.router-and-streams","i-lucide-route",{"title":120,"path":121,"stem":122,"icon":123},"SMTP","\u002Fsending\u002Fsmtp","4.sending\u002F3.smtp","i-lucide-server",{"title":125,"path":126,"stem":127,"icon":128},"Maps","\u002Fsending\u002Fmaps","4.sending\u002F4.maps","i-lucide-map",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":30},"Tooling","i-lucide-wrench","\u002Ftooling","5.tooling",[135,140,145,150],{"title":136,"path":137,"stem":138,"icon":139},"The lttr CLI","\u002Ftooling\u002Fcli","5.tooling\u002F1.cli","i-lucide-terminal",{"title":141,"path":142,"stem":143,"icon":144},"Live preview","\u002Ftooling\u002Flive-preview","5.tooling\u002F2.live-preview","i-lucide-eye",{"title":146,"path":147,"stem":148,"icon":149},"Screenshots","\u002Ftooling\u002Fscreenshots","5.tooling\u002F3.screenshots","i-lucide-camera",{"title":151,"path":152,"stem":153,"icon":154},"Goldens and testing","\u002Ftooling\u002Fgoldens-and-testing","5.tooling\u002F4.goldens-and-testing","i-lucide-flask-conical",{"title":156,"icon":157,"path":158,"stem":159,"children":160,"page":30},"Guides","i-lucide-book-open","\u002Fguides","6.guides",[161,166,170],{"title":162,"path":163,"stem":164,"icon":165},"The Quiet example","\u002Fguides\u002Fthe-quiet-example","6.guides\u002F1.the-quiet-example","i-lucide-mail-open",{"title":167,"path":168,"stem":169,"icon":82},"Your own family and theme","\u002Fguides\u002Fyour-own-family-and-theme","6.guides\u002F2.your-own-family-and-theme",{"title":171,"path":172,"stem":173,"icon":174},"Releasing","\u002Fguides\u002Freleasing","6.guides\u002F3.releasing","i-lucide-tag",{"title":176,"icon":177,"path":178,"stem":179,"children":180,"page":30},"Reference","i-lucide-library","\u002Freference","7.reference",[181,186],{"title":182,"path":183,"stem":184,"icon":185},"API reference","\u002Freference\u002Fapi-reference","7.reference\u002F1.api-reference","i-lucide-list-tree",{"title":187,"path":188,"stem":189,"icon":190},"Gallery","\u002Freference\u002Fgallery","7.reference\u002F2.gallery","i-lucide-images",{"id":192,"title":146,"body":193,"description":724,"extension":725,"links":726,"meta":727,"navigation":728,"path":147,"seo":729,"stem":148,"__hash__":730},"docs\u002F5.tooling\u002F3.screenshots.md",{"type":194,"value":195,"toc":714},"minimark",[196,253,258,281,287,317,320,348,368,372,457,461,475,524,561,564,568,585,617,621,640,644,649,674,687,691,710],[197,198,199,203,204,213,214,217,218,222,223,226,227,226,230,233,234,237,238,241,242,244,245,248,249,252],"p",{},[200,201,202],"code",{},"tools\u002Fshoot\u002Fshoot.py"," is a standalone Python script (run with ",[205,206,210],"a",{"href":207,"rel":208},"https:\u002F\u002Fdocs.astral.sh\u002Fuv\u002F",[209],"nofollow",[200,211,212],{},"uv",", via Playwright) that screenshots every ",[200,215,216],{},"lttr preview"," output at phone and desktop widths, in light and dark, and fails the run if any render scrolls horizontally. It is ",[219,220,221],"strong",{},"not a Go dependency"," — it never runs during ",[200,224,225],{},"go build",", ",[200,228,229],{},"go test",[200,231,232],{},"go mod"," or ",[200,235,236],{},"make check",", and it only touches ",[200,239,240],{},"preview\u002F"," (an ",[200,243,216],{}," output directory), ",[200,246,247],{},"design\u002Fhandoff-11\u002Fdesign\u002F"," (read-only) and its own ",[200,250,251],{},"tools\u002Fshoot\u002Fout\u002F",".",[254,255,257],"h2",{"id":256},"run-it","Run it",[259,260,266],"pre",{"className":261,"code":262,"filename":263,"language":264,"meta":265,"style":265},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","make shoot\n","Terminal","bash","",[200,267,268],{"__ignoreMap":265},[269,270,273,277],"span",{"class":271,"line":272},"line",1,[269,274,276],{"class":275},"sBMFI","make",[269,278,280],{"class":279},"sfazB"," shoot\n",[197,282,283,284,286],{},"which is ",[200,285,216],{}," followed by:",[259,288,290],{"className":261,"code":289,"filename":263,"language":264,"meta":265,"style":265},"uv run tools\u002Fshoot\u002Fshoot.py preview --out tools\u002Fshoot\u002Fout --design design\u002Fhandoff-11\u002Fdesign\n",[200,291,292],{"__ignoreMap":265},[269,293,294,296,299,302,305,308,311,314],{"class":271,"line":272},[269,295,212],{"class":275},[269,297,298],{"class":279}," run",[269,300,301],{"class":279}," tools\u002Fshoot\u002Fshoot.py",[269,303,304],{"class":279}," preview",[269,306,307],{"class":279}," --out",[269,309,310],{"class":279}," tools\u002Fshoot\u002Fout",[269,312,313],{"class":279}," --design",[269,315,316],{"class":279}," design\u002Fhandoff-11\u002Fdesign\n",[197,318,319],{},"Or point it at any preview directory directly:",[259,321,323],{"className":261,"code":322,"filename":263,"language":264,"meta":265,"style":265},"uv run tools\u002Fshoot\u002Fshoot.py PREVIEW_DIR --out OUT_DIR [--design DESIGN_DIR]\n",[200,324,325],{"__ignoreMap":265},[269,326,327,329,331,333,336,338,341,345],{"class":271,"line":272},[269,328,212],{"class":275},[269,330,298],{"class":279},[269,332,301],{"class":279},[269,334,335],{"class":279}," PREVIEW_DIR",[269,337,307],{"class":279},[269,339,340],{"class":279}," OUT_DIR",[269,342,344],{"class":343},"sTEyZ"," [--design ",[269,346,347],{"class":279},"DESIGN_DIR]\n",[197,349,350,352,353,356,357,226,360,363,364,367],{},[200,351,212],{}," reads the inline script metadata at the top of ",[200,354,355],{},"shoot.py"," (",[200,358,359],{},"requires-python",[200,361,362],{},"dependencies = [\"playwright>=1.47\"]",") and installs Playwright into an ephemeral environment with no project virtualenv needed; on first run it also installs the Chromium build Playwright needs if it isn't cached (",[200,365,366],{},"~\u002FLibrary\u002FCaches\u002Fms-playwright",").",[254,369,371],{"id":370},"pass-flags","Pass flags",[373,374,375,391],"table",{},[376,377,378],"thead",{},[379,380,381,385,388],"tr",{},[382,383,384],"th",{},"Flag",[382,386,387],{},"Required",[382,389,390],{},"Meaning",[392,393,394,413,425,442],"tbody",{},[379,395,396,403,406],{},[397,398,399,402],"td",{},[200,400,401],{},"preview_dir"," (positional)",[397,404,405],{},"yes",[397,407,408,409,412],{},"Directory of rendered preview HTML files (an ",[200,410,411],{},"lttr preview -out …"," output)",[379,414,415,420,422],{},[397,416,417],{},[200,418,419],{},"--out DIR",[397,421,405],{},[397,423,424],{},"Directory to write screenshots and the contact sheet to",[379,426,427,432,435],{},[397,428,429],{},[200,430,431],{},"--design DIR",[397,433,434],{},"no",[397,436,437,438,441],{},"Directory of design HTML files to screenshot for comparison (e.g. ",[200,439,440],{},"design\u002Fhandoff-11\u002Fdesign",")",[379,443,444,449,451],{},[397,445,446],{},[200,447,448],{},"--nav-timeout-ms N",[397,450,434],{},[397,452,453,454,441],{},"Per-page navigation timeout in milliseconds (default 15000, or ",[200,455,456],{},"$SHOOT_NAV_TIMEOUT_MS",[254,458,460],{"id":459},"screenshot-every-render","Screenshot every render",[197,462,463,464,467,468,470,471,474],{},"For every ",[200,465,466],{},"{name}.html"," file directly in ",[200,469,401],{}," (skipping ",[200,472,473],{},"index.html","):",[476,477,478,490,508,518],"ul",{},[479,480,481,482,485,486,489],"li",{},"opens it in headless Chromium at ",[219,483,484],{},"600px"," and ",[219,487,488],{},"375px",";",[479,491,492,493,485,496,499,500,503,504,507],{},"in both ",[219,494,495],{},"light",[219,497,498],{},"dark"," colour-scheme emulation (",[200,501,502],{},"browser.new_context(color_scheme=…)"," — a genuine ",[200,505,506],{},"prefers-color-scheme"," emulation, not a page toggle);",[479,509,510,511,485,514,517],{},"reads ",[200,512,513],{},"document.documentElement.scrollWidth",[219,515,516],{},"fails that shot if it exceeds the viewport width"," — i.e. the render scrolls horizontally;",[479,519,520,521,252],{},"saves a full-page PNG as ",[200,522,523],{},"OUT\u002F{name}-{width}-{scheme}.png",[197,525,526,527,529,530,467,533,536,537,540,541,544,545,548,549,552,553,556,557,560],{},"With ",[200,528,431],{},", it additionally opens every ",[200,531,532],{},"*.html",[200,534,535],{},"DIR"," (no filenames are hardcoded) at a large viewport and screenshots every ",[200,538,539],{},"section[data-screen-label]"," element to ",[200,542,543],{},"OUT\u002Fdesign\u002F{slug(label)}.png",", so a render can be compared side by side with the original handoff frame it was built from. A label matching ",[200,546,547],{},"^Q(\\d+)\\b"," (e.g. \"Q1 Order shipped\") is that template's desktop frame; ",[200,550,551],{},"^Mobile Q(\\d+)\\b"," is a mobile frame — the digits, zero-padded to two, are matched against the preview file's leading ",[200,554,555],{},"{NN}-"," number. This mapping is derived at runtime from whatever ",[200,558,559],{},"data-screen-label"," text the design HTML contains, not a hardcoded list.",[197,562,563],{},"Each render or design frame is isolated — one failure (a navigation timeout, a section that won't screenshot) doesn't abort the run or the contact sheet; it's recorded and shown in the summary and the contact sheet's \"Errors\" panel instead.",[254,565,567],{"id":566},"read-the-contact-sheet","Read the contact sheet",[197,569,570,573,574,577,578,580,581,584],{},[200,571,572],{},"OUT\u002Findex.html"," is a static page, ",[219,575,576],{},"light mode by default"," (never following the OS ",[200,579,506],{}," automatically — only its own \"Toggle dark mode\" button changes it, remembered in ",[200,582,583],{},"localStorage","). It shows:",[476,586,587,590,593,596,607,614],{},[479,588,589],{},"a status line: how many renders scrolled horizontally or errored, or \"all N render(s) fit their viewport\";",[479,591,592],{},"an \"Errors\" panel (only when something failed) listing each failure's file, width\u002Fscheme (or design label) and message;",[479,594,595],{},"one row per template: \"Our render\" (600px and 375px, light and dark) and, when a matching design frame was captured, a \"Design handoff\" row underneath with the desktop and mobile frame(s);",[479,597,598,599,602,603,606],{},"a shot outlined in red and labelled ",[219,600,601],{},"\"FAIL (scrolls)\""," — its ",[200,604,605],{},"scrollWidth"," exceeded its viewport, meaning the render itself needs a fix before merging;",[479,608,609,610,613],{},"a shot outlined in red with no image and an ",[219,611,612],{},"\"ERROR: …\""," caption — the render failed outright (e.g. never finished loading) rather than scrolling;",[479,615,616],{},"click any thumbnail to open the full-resolution PNG in a new tab.",[254,618,620],{"id":619},"catch-horizontal-scroll","Catch horizontal scroll",[197,622,623,624,627,628,630,631,634,635,639],{},"The check is exactly ",[200,625,626],{},"scroll_width \u003C= width"," for each shot's viewport. Anything that pushes ",[200,629,513],{}," past 600px (desktop) or 375px (mobile) — a fixed-width table too wide for the mobile column, an image without a max-width, an un-wrapped long word — fails that shot, marks the run as failed, and shows up outlined in red on the contact sheet. Since email HTML runs everywhere without a build step to catch this, ",[200,632,633],{},"tools\u002Fshoot"," is the only mechanical check that email markup actually fits its declared width; the ",[205,636,638],{"href":637},"\u002Ftooling\u002Fgoldens-and-testing#run-the-structural-lint","structural lint"," catches other classes of mistakes but not layout overflow.",[254,641,643],{"id":642},"read-the-exit-status","Read the exit status",[197,645,646,648],{},[200,647,355],{}," prints a summary table (file, width, scheme, scrollWidth, ok) and exits:",[476,650,651,668],{},[479,652,653,656,657,659,660,663,664,667],{},[219,654,655],{},"1"," if any render scrolled horizontally or errored (a navigation timeout, a design frame that failed to capture, …), or if ",[200,658,401],{},"\u002F",[200,661,662],{},"--design"," doesn't exist (exit ",[219,665,666],{},"2"," for that case specifically);",[479,669,670,673],{},[219,671,672],{},"0"," otherwise.",[197,675,676,679,680,682,683,686],{},[200,677,678],{},"make shoot","'s definition of M8 done (SPEC §16): ",[200,681,216],{}," output has no horizontal scroll at 375px in either colour scheme, and the screenshots look right next to ",[200,684,685],{},"design\u002Fhandoff-11\u002Fscreenshots\u002F"," when reviewed by eye.",[254,688,690],{"id":689},"next-steps","Next steps",[476,692,693,705],{},[479,694,695,697,698,701,702,704],{},[205,696,141],{"href":142}," for ",[200,699,700],{},"make preview",", which ",[200,703,678],{}," runs first.",[479,706,707,709],{},[205,708,151],{"href":152}," for the structural lint the Go test suite runs on every golden.",[711,712,713],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":265,"searchDepth":715,"depth":715,"links":716},2,[717,718,719,720,721,722,723],{"id":256,"depth":715,"text":257},{"id":370,"depth":715,"text":371},{"id":459,"depth":715,"text":460},{"id":566,"depth":715,"text":567},{"id":619,"depth":715,"text":620},{"id":642,"depth":715,"text":643},{"id":689,"depth":715,"text":690},"make shoot, tools\u002Fshoot's flags, the contact sheet it builds, and why a horizontal scroll fails the run.","md",null,{},{"icon":149},{"title":146,"description":724},"jC7oXhNEDM5MgkqSSnkTOA43YcfLNf4fYCLwscTOnOQ",[732,734],{"title":141,"path":142,"stem":143,"description":733,"icon":144,"children":-1},"lttr serve's reload loop, its error display, the index page's controls, and how make preview differs.",{"title":151,"path":152,"stem":153,"description":735,"icon":154,"children":-1},"make golden and reviewing its diffs, the structural lint, make check, fuzzing, the coverage floor, and testing your own templates.",1790309826684]