feat(marketing): generated marketing suite β 1,600+ pages, animated walkthroughs, social image suite - #610
feat(marketing): generated marketing suite β 1,600+ pages, animated walkthroughs, social image suite#610lopugit wants to merge 9 commits into
Conversation
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:41 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
β¦00+ pages, animated walkthroughs, social image suite
Lopu repository reviewLopu reviewed this PR against develop as Thingtime's principal PR and repository manager. Using Claude Opus 5. Lopu found no justified local change to publish from this review pass. Lopu review β PR #610
|
| Run | Event | Analyze outcome |
|---|---|---|
34015726762 |
push |
skipped β "An open PR owns β¦" |
34015734529, 34015751559, 34015832287, 34015836414 |
pull_request_target |
metadata-only pr-handoff; control-plane job skipped |
34015739469, 34015756581, 34015835469, 34015839875 |
dispatched backfills | skipped β base carries the listener and the PR is mergeable |
34015734568 |
pull_request |
sole owner |
That sole owner's attempt 1 split its two matrix languages far apart under
Actions queue pressure:
Analyze (actions)06:08:49 β 06:09:47β analysis1731109674uploaded at
06:09:40(0 results).Analyze (javascript-typescript)queued, started06:10:59, finished
06:18:24β analysis1731125482uploaded at06:18:17.
Advanced Security opened the PR's aggregate CodeQL check at 06:09:39 when
the first snapshot landed and concluded it timed_out at 06:09:41, 8m37s
before the second language arrived.
Attempt 2 (re-run, 10:22:29 β 10:29:57) uploaded both languages
successfully for the same merge commit d4cd8f97 (analyses 1731551906 at
10:23:25 and 1731563602 at 10:29:49). Advanced Security did not reopen the
already-concluded check β the same "closed timed_out and never reopened"
behaviour already recorded in codeql-analysis.yml's PR #557 incident note.
Why I did not "fix" it. The check is terminal and bound to head
780c81f9; a third re-run cannot clear it, because Advanced Security will not
reopen a concluded aggregate check. It clears on its own the next time the
branch advances (new head SHA β new check suite). Greening it by any other means
would be cosmetic. Nothing in the PR's code contributed to it.
Residual control-plane gap (observation, not repaired here). The existing
hardening in codeql-analysis.yml prevents duplicate analyses racing under
different refs. It does not cover this shape: a single, correctly-elected owner
whose two matrix languages are scheduled minutes apart still crosses the
aggregate-check wait window. This is a protected-controller concern
(.github/workflows/**), so it must not ride on this PR's branch, and I did not
open a partial controller repair under an active cooperative handover β an
unfinished controller change is worse than a documented one. Logged here as
follow-up work.
Content review
The suite is a self-contained, generated marketing site derived from one
catalog. Reviewed for correctness, security, performance, compatibility and
tests before style.
Security β clean. The two surfaces that could bite are both explicitly
handled and test-pinned:
- Prototype pollution / unsafe lookup. Every key β entry map is built through
app/marketing/lookup.tsbyKey(), which usesObject.create(null). Guards
written asif (MAP[key])therefore answer only for real entries, so a URL
segment or query value namingconstructor/toString/__proto__
resolves to nothing and takes the normal not-found path. The rationale and the
concrete bugs it fixes are documented in the file. - SVG injection.
app/marketing/social.tsbuilds SVG markup as strings, but
every text node is wrapped inescapeXml()(& < > " '). The unescaped
interpolations are numbers and authored catalog constants (fonts, palette
colours, keys) β no user-controlled value reaches raw markup. The only inputs
from the URL (feature,trend,format) are resolved through the
null-prototype maps above and fall back to not-found. - No
dangerouslySetInnerHTML,innerHTML,eval,new Functionor
document.writeanywhere inapp/marketing/,app/components/Marketing/or
app/routes/marketing/.
Browser helpers β correct. marketingDownload.ts is SSR-safe (no
document / URL / navigator at module scope; server calls reject with a
clear Error or resolve false). rasteriseSvg revokes its object URL in a
finally, so a decode failure cannot leak it; triggerDownload revokes after a
bounded delay. downloadSequentially never throws β a failing item is counted
and the batch continues β and DOWNLOAD_BATCH_CAP (40) plus the pure
planDownloads helper keep a "download all" from wedging a browser, reporting
what it skipped rather than silently truncating.
Performance / blast radius β contained. All four routes are registered
lazy in app/routes.tsx, so catalog.ts (which enumerates the full page set
eagerly at module scope) is never loaded for non-marketing traffic. The
root.tsx change correctly matches pathname === '/marketing' || startsWith('/marketing/') rather than a bare startsWith('/marketing'), so
/marketingfoo still falls through to the catch-all tree viewer and keeps its
own title β the comment calls out exactly that trap.
Tests / conventions. test:marketing is wired into test:unit, so this
lands in the normal gate rather than beside it. Six test files cover the
catalog, page grouping, walkthrough engine, download planning, mock screens and
the sample tree, and the docs (docs/marketing-suite.md) plus the TESTING.md
manual checklist are both updated. Data access conventions are not implicated:
the suite is pure client-side generated content with no API or MongoDB surface.
No correctness, security, crash, compatibility or maintainability defect found
that warrants a change.
Changes made
None. The PR's code is sound, and the only red signal on it is the stale
Advanced Security check diagnosed above, which no edit to this branch would fix.
Making a change here purely to produce a commit would be unjustified churn.
CodeQL dispositions file left as initialized ([]) β there are no alerts on this
head to fix or dispose of.
Validation
node_modules is absent in the review sandbox and test:marketing needs tsx,
so I ran the PR's real test files against the real sources through node's
built-in TypeScript type-stripping plus a throwaway extension-resolving loader
hook (written to $RUNNER_TEMP, outside the worktree β no repository file was
touched):
| File | Result |
|---|---|
app/marketing/catalog.test.ts |
15/15 pass |
app/marketing/pageGroups.test.ts |
5/5 pass |
app/components/Marketing/marketingDownload.test.ts |
10/10 pass |
app/components/Marketing/walkthroughEngine.test.ts |
16/16 pass |
46/46 runnable tests pass. MockScreens.test.ts and SampleTree.test.ts
could not run here β both fail at import with
ERR_MODULE_NOT_FOUND: Cannot find package 'react', which is the absent
dependency tree in this sandbox, not a defect in the PR. They are covered by
test:marketing in normal CI.
Check-state evidence was read live from gh pr checks, the check-runs API, the
Actions runs/jobs API (including attempt 1 of run 34015734568) and the
code-scanning analyses API for refs/pull/610/merge, refs/pull/610/head and
refs/heads/claude/marketing-suite.
Handover
A cooperative handover was requested part-way through this session. PR #610 was
carried to completion β diagnosis, review, validation and this report. PRs
#683, #590, #557 and #10 were not reviewed and were left untouched, with
no reports and no worktree changes.
π€ Lopu detected an out-of-date PR branchStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:59 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
β¦00+ pages, animated walkthroughs, social image suite
β Develop S3 preview ready
The alias passed the develop bucket CORS preflight and a final live PR/SHA fence. Generic Vercel Preview deployments use the shared development runtime; this controller adds the stable exact-SHA alias and marker-scoped cleanup. |
|
π€ Lopu is rebasing this stack β work started at 13:36 UTC. Expected to finish around 13:51 UTC (most rebases still land in 5-15 minutes; the hosted-runner ceiling is 360). On success this branch is force-pushed onto its new base and a result comment follows β no manual action is needed meanwhile. |
|
Lopu review β PR 610 (head The failing Why the check is redCheck-run The check simply gave up before the analysis landed:
GHAS refreshed the check's summary from those later clean analyses but never reset the terminal Open code-scanning alerts: 0 on both The real trigger is that the js-ts analysis (~9.5 min) outruns the GHAS results-check wait window. That knob is GitHub-side rather than in Code reviewReviewed the PR's own contribution via the three-dot diff from merge base No correctness, security, crash, compatibility or coverage defect found. Things I specifically went after, all clean:
One non-blocking nit, deliberately not changed: ValidationThe review worktrees have no dependency trees, so rather than a partial local run I verified against CI on this exact head SHA:
Nice work β the defensive commentary in |
π€ Lopu detected an out-of-date PR branchStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 07:12 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected an out-of-date PR branchStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 07:40 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected an out-of-date PR branchStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 08:09 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
Lopu review β PR #610 (head Re-reviewed this head as part of the post-conflict-resolution batch (run 34089423857). The code review stands and the verdict stands β the red Correction: this is fixable in
|
| When | Category |
|---|---|
06:09:39Z |
aggregate CodeQL check opens |
06:09:40Z |
/language:actions uploads |
06:09:41Z |
aggregate check closes timed_out β a 2-second lifetime |
06:18:17Z |
/language:javascript-typescript uploads β 8m37s after actions |
That 2-second lifetime is the load-bearing detail, and it revises the mechanism. A ~6m30s processing-lag timeout would have held the check open for ~6.5 minutes; this one closed in two seconds. So on this PR the check did not wait and give up β it decided immediately, because when the actions SARIF landed no sibling configuration was in flight for that SHA at all (the js-ts leg had not yet been allocated a runner). This is the 1 configuration not found shape, not the processing-lag shape.
Both shapes are real; they just need different guards.
Where the fix lives
#663 repairs both, in .github/workflows/codeql-analysis.yml:
- a sibling-start barrier before
init, gated onstrategy.job-totalβ so the first upload can never land alone. This is the guard that covers this PR's 2-second failure. - an ordering hold releasing on the js-ts analysis appearing in
code-scanning/analysesβ which covers the processing-lag shape, and makes the slow language the first uploader so the window size stops mattering.
I verified #663's contract test on its head: node .github/scripts/workflow-control-plane-contract.mjs --self-test β self-test OK.
#686 is a rival repair of the same defect and has no start barrier, so it would not have prevented what happened here. The two conflict β git merge-tree on their heads reports CONFLICT (content): .github/workflows/codeql-analysis.yml β so only one can land. My recommendation remains: land #663, close #686.
What this means for this PR
Nothing to do here. No change was made and the worktree is left clean at 780c81f9. The red check will clear on the next scan once #663 lands; it will not clear by re-running anything on this branch, because GHAS never resets a terminal conclusion on a SHA it has already decided β which is why the clean 10:23:35Z/10:29:56Z re-analyses left it red.
Merge is not blocked (UNSTABLE, not BLOCKED), and 0 CodeQL alerts are open on this head (trusted snapshot []), so there was nothing to fix or dispose of.
β Lopu, automated repository review
|
Lopu review β the red I chased it to ground before touching anything, because a What the check actually saysCheck run Root cause: matrix skew inside the one correctly-elected ownerHead
That sole owner's first attempt got its two matrix languages scheduled far apart under Actions queue pressure:
Advanced Security opened this PR's aggregate check at Attempt 2 (the re-run at Why I left it aloneThe check is terminal and bound to head There is a residual gap worth naming: the existing hardening stops duplicate analyses racing under different refs, but it doesn't cover this shape β a single, correctly-elected owner whose matrix languages drift minutes apart still crosses the aggregate-check window. That lives in On the code itself β no changes neededReviewed the 46 non-
Validation:
No defect found that warrants a change, so I made none β I'm not going to manufacture a commit to look busy. β Lopu |
β¦alkthroughs, social image suite
/marketing is a catalog-generated marketing site: feature landing pages,
how-to guides, animated cursor walkthroughs over mock screens, fair
competitor comparisons and alternatives, audience pages, use cases, concept
explainers, starter templates, twelve style editions per lead feature, FAQ
pages and getting-started checklists β 1,635 pages from one data set
(remix/app/marketing/), every slug validated, every link resolved,
hero-first and CTA-last, deterministic per slug.
/marketing/social-media is a menu-navigable image suite: every feature Γ
twelve trend styles Γ ten platform formats renders as a self-contained SVG
and downloads as PNG at the exact platform size, with per-platform captions
and hashtags.
Routes are lazy (marketing, marketing/social-media, marketing/:category,
marketing/*); the root title effect defers to useMarketingSeo under
/marketing; the drawer gains a Marketing hub. Tests: npm run test:marketing
(47 checks); manual checklist in TESTING.md ("Marketing suite"); design
notes in docs/marketing-suite.md. The use-case headline helper is named
headlineForUseCase so the hooks lint rule does not mistake it for a hook.
Rescued from the stranded Claude worktree session-ffdf75 (authored
2026-09-03, never committed).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
β¦over The stranded workflow that built the marketing suite died on a usage limit before the agent assigned to MockScreens.tsx wrote it, so WalkthroughPlayer imported a module that did not exist and every /marketing page failed to build. This adds the ten mock product screens (feed, things, builder, messages, themes, components, settings, developer, search, profile) as plain elements styled from the --mk-* variables. Each screen renders every SCREEN_TARGETS entry exactly once as data-wt, shows the walkthrough's typed text inside input-like targets (and in place of the hero copy), and rings the active target. MockScreens.test.ts pins the two-way agreement with SCREEN_TARGETS and checks every walkthrough step resolves on its screen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
β¦nter Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Refreshed by the rebase-pr-stacks workflow: https://github.com/lopugit/thingtime/actions/runs/34097259555 Lopu-Rebase-Completion: run=34097259555 pr=610
|
𧬠Rebased
The branch was published once with an exact force-with-lease. Please review the rewritten commits before relying on them. |
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 12:05 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:02 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 10:03 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 10:08 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 10:26 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 10:37 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 12:58 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
The PR changed refs or resolver ownership, so no pause was added; the current state remains eligible for automatic re-detection. |
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:39 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:48 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
|
β Develop S3 preview ready
4722944cdevelopThe alias passed the develop bucket CORS preflight and a final live PR/SHA fence.
Generic Vercel Preview deployments use the shared development runtime; this controller adds the stable exact-SHA alias and marker-scoped cleanup.
Summary
Rescued from a stranded Claude worktree (
session-ffdf75, authored 2026-09-03, never committed or pushed). This PR publishes that work unchanged apart from one lint rename./marketingis a catalog-generated marketing site: feature landing pages, how-to guides, animated cursor walkthroughs over mock screens, fair competitor comparisons and alternatives, audience pages, use cases, concept explainers, starter templates, twelve "style editions" per lead feature, FAQ pages and getting-started checklists. 1,635 pages come from one data set (remix/app/marketing/): every slug validated, every link resolved, hero-first and CTA-last, deterministic per slug./marketing/social-mediais a menu-navigable image suite: every feature Γ twelve viral trend styles Γ ten platform formats renders as a self-contained SVG and downloads as PNG at the exact platform size, with per-platform captions and hashtags (10,680 images).marketing,marketing/social-media,marketing/:category,marketing/*); the root title effect defers touseMarketingSeounder/marketing; the drawer gains a Marketing hub.docs/marketing-suite.md; the manual checklist is the "Marketing suite" section ofTESTING.md;remix/CHANGELOG.mdcarries the grouped summary.useCaseHeadlineβheadlineForUseCase(thereact-hooks/rules-of-hooksrule treated theuseprefix as a hook call inside a plain builder function).Verification
npm run test:marketingβ 47/47 (catalog validation, walkthrough target coverage, SVG rendering, copy determinism, mock-screen coverage, player engine).pnpm run lint:fileson every changed file β clean after the rename.Notes
π€ Generated with Claude Code