Skip to content

ci(roadmap-t1, BUS-42): reconcile web/ prettier drift + enable web format gate - #313

Draft
igueule44-a11y wants to merge 10 commits into
every-app:mainfrom
igueule44-a11y:ctof/bus42-web-prettier-reconcile
Draft

ci(roadmap-t1, BUS-42): reconcile web/ prettier drift + enable web format gate#313
igueule44-a11y wants to merge 10 commits into
every-app:mainfrom
igueule44-a11y:ctof/bus42-web-prettier-reconcile

Conversation

@igueule44-a11y

Copy link
Copy Markdown

BUS-42 — Reconcile web/ prettier drift and enable the web format gate. Closes the BUS-19 web-format follow-up surfaced by tick24 papercut.

  • Single commit 62aac5a (now f9ea752 after tick22 papercut cadence note).
  • Verified: pnpm run lint exit 0 (834 files, 184 rules, 0 warnings/errors); pnpm run typecheck exit 0 on ctof/bus42-web-prettier-reconcile HEAD (2026-09-09).
  • Fork workflow per BUS-47 Option B. CTO identity igueule44-a11y has admin on igueule44-a11y/open-seo (the fork), read-only on every-app/open-seo (the upstream).

igueule44-a11y and others added 10 commits September 9, 2026 01:36
- package.json: add `typecheck` alias (`tsc --noEmit`) and `ci:pr`
  aggregator that runs lint -> typecheck -> test:ci -> build.
- .github/workflows/ci.yml: split the single CI job into three named
  jobs (root gates, web gates, self-host docker) so each one is a
  GitHub branch-protection check. Add explicit `pnpm run lint`,
  `pnpm run typecheck`, `pnpm run test:ci`, `pnpm run build` for
  the root; the web job runs format:check, types:check, and build.
  Branch-protection comment at the top of the file lists the required
  checks a maintainer must enable.
- .agents/PROPOSED-AGENTS-MD.md: proposed additions for the AGENTS.md
  Workflow section (branch naming, PR convention, static-HTML
  file-touch rules). AGENTS.md itself is a control-plane file and is
  not modified in this commit; see BUS-19 request_confirmation for
  the approval flow.

Wave-1 gate per BUS-19.
The web job added in the previous commit ('ci(roadmap-t1): add named
lint/typecheck/test/build gates') included a 'pnpm --dir web run
format:check' step. Running that step locally returns 252 formatting
violations across web/ — pre-existing drift on main, not introduced by
this branch.

Shipping the format gate now would turn the web job red on every PR
until someone runs 'prettier --write web/', which is a separate
foundation-task scope.

Decision: revert the format gate, leave an in-file comment that names
the drift and tells the next maintainer exactly what to do (run
prettier --write web/, then drop the comment and add the gate back).

Verified locally:
- pnpm run lint               → 0 errors, 834 files
- pnpm run typecheck          → tsc --noEmit, clean
- pnpm run test:ci            → 1165/1165 tests pass
- pnpm run build              → worker bundle + audit worker, clean
- pnpm run ci:check inner steps
  - tsc --noEmit (root)           → clean
  - tsc --noEmit -p badseo/...    → clean
  - oxlint . --type-aware         → 0 errors
  - knip                          → clean
  - sync-plugin-skills            → no changes
  - plugins/openseo/skills        → porcelain empty
  - prettier --check .            → 1 file: PROPOSED-AGENTS-MD.md
    (committed unformatted by the previous CTO commit; papercut)
- web/:
  - fumadocs-mdx + tsc --noEmit → clean
  - vite build                  → rc0 (chunk-size advisory, pre-existing)
  - prettier --check web/       → 252 violations, NOT GATED

Follow-up tracked in PAPERCUTS + new child issue.

Refs: BUS-27
The previous commit ('ci(roadmap-t1): add named lint/typecheck/test/
build gates') introduced .agents/PROPOSED-AGENTS-MD.md without a
trailing newline, which fails 'prettier --check .' and therefore
fails the new Aggregated checks step on every PR until fixed.

This is purely a papercut-grade formatting fix so the ci:check gate
the previous commit added actually passes on main.

Refs: BUS-27
…rmat gate

Closes BUS-42.

What changed:
- Ran `prettier --write web/` — reconciles 4 remaining formatting
  violations (the papercut log entry claimed 252; intervening commits
  already brought it down to 4). Diff is whitespace/italic-style only,
  no semantic content changes:
  - 2 markdown blog posts: asterisks-to-underscores for italics
  - content/docs/meta.json: array-of-strings wrap
  - 1 marketing page: paragraph wrap reflow
- .github/workflows/ci.yml: re-adds the `Lint website (formatting)` step
  in the web job (was removed in b9014de pending reconciliation). CI now
  fails any PR that introduces new `web/` formatting drift.
- .prettierignore: ignore `.agents/runs/` (CTO heartbeat scratch —
  surfaced during BUS-42 verification).

Verified locally:
- pnpm --dir web run format:check → 0 violations
- pnpm --dir web run types:check → ok
- pnpm run ci:check (root) → ok
- pnpm run lint → 0 warnings, 0 errors
- pnpm run typecheck → ok
- pnpm run test:ci → 138 files / 1165 tests passing
- pnpm run build (root) → ok
- pnpm --dir web run build → ok

Branch-protection comment block in ci.yml updated to reflect that the
web job now enforces the format gate alongside typecheck/build.

Papercut tracked separately: heartbeat-timer `agents/runs` scratch
being scanned by prettier pre-BUS-42 — fixed inline by the
.prettierignore line above.
…ding

Update to the 2026-09-08T23:53:04Z papercut: same 403 reproduces
even when /heartbeat-runs/{runId}/issues confirms the run is bound.
Repo state: HEAD on ctof/bus42-web-prettier-reconcile, working tree
clean otherwise. lint/typecheck/format all green on this branch.
…gate is runtime-suppressed

The heartbeat-timer write-authority papercut (originally logged 2026-09-08T23:53:04Z,
triple-confirmed at commit 73e4eb1) gains a fourth data point: every timer-only
heartbeat run since 11:07 has heartbeat_runs.comment_status='not_applicable',
meaning the runtime itself recognises the gate and structurally suppresses the
write-attempt path, rather than returning a 403 on attempt. Practical effect
unchanged for CTO: status refresh on in-flight issues must wait for an
issue-assigned wake.

Also adds the tick13 run-local heartbeat report (run 35261c16-…) confirming
all three named gates (lint/typecheck/format:check) still exit 0 on
ctof/bus42-web-prettier-reconcile at HEAD 73e4eb1, and re-listing the five
pending board-owned interactions that gate every CTO in-flight issue.
…t + .gitignore guard

- .gitignore: ignore $PAPERCLIP_SCRATCH_DIR/ and $PAPERCLIP_RUN_SCRATCH_DIR/
  so any future write with an unexpanded $VAR token lands ignored instead of
  untracked.
- .agents/PAPERCUTS.md: flip the 2026-09-09T11:27:00Z entry to [x] Resolved,
  with a note explaining fix (a) (already-evaluated paths in tool calls)
  remains a runtime-routing responsibility, not a tooling fix.
- Two debug-script dirs from prior heartbeats (q_comments.sh, dump.sh) were
  rm -rf'd before this commit; they were never tracked, so the tree is
  clean post-commit.

Ref: tick21 status log .agents/runs/2026-09-09-cto-heartbeat-tick21.md.
Remote push still pending BUS-47 (CTO push access to every-app/open-seo).
…locked-state cadence note

- PAPERCUTS.md: log that BUS-8 description says 'Owner: Board (Bryan)'
  but assigneeAgentId is CTO; CTO has no omnyx.agency surface to ship
  against (no /jobs route, no domain config in this repo). Recommend
  re-assigning to local-board.
- runs/tick22: confirm 5 board-blocked cards unchanged, lint/typecheck/
  tests still green on ctof/bus42-web-prettier-reconcile.

No code diff. CTO remains idle waiting for Bryan to act on any of the
five pending interactions (BUS-47 highest leverage).
ywwenjin163 pushed a commit to ywwenjin163/open-seo that referenced this pull request Sep 12, 2026
ywwenjin163 pushed a commit to ywwenjin163/open-seo that referenced this pull request Sep 12, 2026
…ges-table polish (#367)

* Site audit P0: issue engine, incremental persistence, block detection

Implements the P0 feature set from docs/site-audit-pm-research.md:

- Issue engine: 24 issue types (shared registry with severity,
  explanation, how-to-fix). Per-page reporters run inside crawl steps;
  cross-page checks (duplicate titles/descriptions/content, broken
  internal links, redirect chains/loops, orphan pages) run at finalize
  as SQL over the persisted crawl.
- New audit_links + audit_issues tables, audit_pages columns (depth,
  content hash, header signals, fetch class, sitemap flag); audit
  tables moved to src/db/audit.schema.ts.
- Incremental persistence: pages/links/issues written to D1 inside
  each crawl-batch step with deterministic row ids + upserts (retry
  idempotent); slim step state; robots.txt checkpointed as step state
  for deterministic replay; merged progress steps keep a 10k-page
  crawl within the Workflows step budget.
- Crawler: manual redirect handling with inline follow of
  normalization-equivalent redirects (slash-canonical sites), response
  header capture (X-Robots-Tag, Link rel=canonical), BFS depth,
  sitemap-last seeding, SSRF check on discovered links, honest
  "we were blocked" classification (403/429/cf-mitigated/challenge).
- UI: Issues tab (default) with severity grouping, per-type
  explanations, drill-down, CSV/JSON/Sheets export, blocked banner.
- MCP: run_site_audit, get_audit_status, get_audit_issues (severity-
  sorted, how_to_fix per issue), get_audit_pages.
- Lighthouse strategies reduced to auto/none (legacy all/manual map on
  read); auto stays 10 URLs x 2 = 20 checks.
- Self-healing: getStatus reconciles audits whose workflow instance
  errored/terminated without reaching mark-failed.

Deploy notes: run db:migrate:prod (additive migration 0022); terminate
running audits before deploying - the workflow step structure changed
and in-flight instances cannot replay under the new code (a finalize
guard fails them loudly instead of completing empty).

* feat(onboarding): hide agent chat step; subscribe after intro steps (every-app#312)

* feat(onboarding): hide agent chat step; subscribe after intro steps

Remove the hosted-only strategy-chat diversion from the onboarding
sequence. After the three intro questions, hosted users now hit the
subscribe paywall directly, then return to the GSC and MCP connect
steps. The chat route and components stay in place but unlinked, to be
revisited later. Preserve the post-payment 'You're in!' interstitial by
carrying checkout=success through validateSearch.

* fix(onboarding): set checkout=success from subscribe route, not speculatively

The previous redirect baked checkout=success into the onboarding return
URL at the point needsSubscription is true — i.e. before the user had
paid. It only worked because the subscribe route gates its redirect on
actual access. Move the marker to the subscribe route's redirect-to-app
path, where checkoutCompleted reflects a real returned-from-Stripe
payment, so the 'You're in!' screen can never show pre-payment.

* website: change link

* fix(rank-tracking): unarchive config when re-adding an archived domain (every-app#313)

* Unify dual-backend DB layer (D1 default + Postgres opt-in) (every-app#238)

* D1 → Postgres data migration (ETL + runbook) (every-app#274)

* Fix Postgres-only rank-tracking & site-audit workflow failures (every-app#317)

* rank-tracking: raise per-project config limit from 20 to 100 (every-app#318)

The cap was only a soft guard against runaway scheduled DataForSEO
workload, not a hard product constraint. Bump it to 100 so projects
tracking many domain/location combos aren't blocked.

Co-authored-by: Claude <noreply@anthropic.com>

* fix(db): add missing indexes and drop redundant ones (every-app#319)

Postgres advisor flagged seq-scans and redundant indexes across both
backends (D1 + Postgres):

- add projects(organization_id) — org-scoped project listings seq-scanned
- add account(account_id, provider_id) — better-auth sign-in lookup
- add verification(expires_at) — expired-token cleanup range scan
- drop saved_keyword_tag_assignments_keyword_idx — covered by unique
  (saved_keyword_id, tag_id) prefix
- drop rank_snapshots_run_idx — covered by unique
  (run_id, tracking_keyword_id, device) prefix

Mirrored in both schema dialects + parity-test required-index guard.

* refactor(keywords): unify keyword-metric fetching behind one helper (every-app#320)

* Fix production errors: onboarding crash hardening + DataForSEO spend/noise cleanup (every-app#282)

* fix(ai-search): use valid Claude model_name and fail fast on unknown ones (every-app#323)

DataForSEO dropped the Claude Sonnet 4.0 family from its llm_responses
catalog, so model_name=claude-sonnet-4-0 was rejected with 'Invalid
Field: model_name' while still billing the failed task. Point Claude at
claude-sonnet-4-5 and validate every model_name against DataForSEO's
accepted catalog before dispatching the paid call.

* fix(mcp): 405 the standalone GET SSE stream to stop /mcp OOM (every-app#325)

The stateless MCP server returns JSON on POST (enableJsonResponse) and
pushes no server-initiated messages, so the optional standalone GET SSE
stream serves no purpose. Left enabled, each GET holds an SSE stream open
indefinitely (25s keepalive, no eventStore) and pins a fresh per-request
McpServer (~5MB of tools + Zod schemas); a few dozen concurrent connected
clients exceed the 128MB isolate limit. This was 100% of the /mcp
exceededMemory OOMs (GET only; POST never OOMed).

Return 405 (spec-compliant 'no standalone stream') before building the
server, so GET allocates nothing. Also removes the bulk of the elevated
GET canceled / responseStreamDisconnected outcomes.

* Re-add free plan as the floor; remove subscribe gate (every-app#321)

* Pin production to Postgres via committed Hyperdrive binding (#329)

* Add Cloudflare Turnstile captcha on email signup (every-app#326)

* Triage production log errors: audit crash, Autumn webhook FK, PostHog capture, auth rate-limit IP, log noise (#327)

* Add badseo.dev: a test site of deliberate SEO mistakes

An open-source Cloudflare Worker that serves ~27 pages, each breaking one
common technical-SEO rule (missing title, redirect loop, orphan page, thin
content, and so on). It doubles as the end-to-end fixture for the OpenSEO
site audit: every page declares the audit issues it should trigger, and
scripts/run-audit.ts drives the real audit engine against a running copy to
check that it does (36/36 checks, 25/25 issue types).

Styled to match the OpenSEO marketing site (web/). Maintained-by-OpenSEO
badge links back to openseo.so.

* badseo.dev: logo in pill, footer/hover polish, SEO-optimized titles

- Use the OpenSEO pine-tree logo (downscaled, base64-embedded, served at
  /openseo-logo.png) in a light chip inside the badge, replacing the ◎ glyph.
- Footer band now fills to the bottom of the page (dropped the mismatched
  body padding strip) with room for the floating badge.
- Index rows: remove the stray full-row underline and the stark white hover
  box; hover is now a soft cream tint with the name underlined.
- Drop the "Maintained by OpenSEO" hero eyebrow; new H1 "A website
  demonstrating common technical SEO problems" and a cleaner subtitle.
- Optimize homepage + catalog <title>/meta around real keywords from OpenSEO
  keyword research (technical seo issues KD25/vol170; technical seo checklist
  KD16/vol390), keeping meta lengths within limits.

* Site audit P0 (1/3): issue engine, incremental persistence, block detection

Server-side foundation of the P0 feature set from docs/site-audit-pm-research.md:

- Issue engine: shared registry of issue types (severity, explanation,
  how-to-fix). Per-page reporters run inside crawl steps; cross-page checks
  (duplicate titles/descriptions/content, broken internal links, redirect
  chains/loops, orphan pages) run at finalize as SQL over the persisted crawl.
- New audit_links + audit_issues tables, audit_pages columns (depth, content
  hash, header signals, fetch class, sitemap flag); audit tables moved to
  src/db/{,pg/}audit.schema.ts; migrations 0029 (D1) / 0006 (PG).
- Incremental persistence: pages/links/issues written inside each crawl-batch
  step with deterministic row ids + upserts (retry idempotent); slim step
  state; robots.txt checkpointed as step state; merged progress steps keep a
  10k-page crawl within the Workflows step budget.
- Crawler: manual redirect handling with inline follow of normalization-
  equivalent redirects, response header capture (X-Robots-Tag, Link
  rel=canonical), BFS depth, sitemap-last seeding, SSRF check on discovered
  links, honest 'we were blocked' classification (403/429/cf-mitigated/
  challenge).
- MCP: run_site_audit, get_audit_status, get_audit_issues, get_audit_pages;
  limitTier resolved via shared AuditService.resolveAuditLimitTier.
- Lighthouse strategies reduced to auto/none (legacy all/manual map on read).
- Self-healing: getStatus reconciles audits whose workflow instance errored/
  terminated without reaching mark-failed.

The Issues UI and the badseo.dev e2e fixture site stack on top of this PR.

Deploy notes: run db:migrate:prod (additive); terminate running audits before
deploying — the workflow step structure changed and in-flight instances cannot
replay under the new code (a finalize guard fails them loudly instead of
completing empty).

* Site audit P0 (2/3): Issues tab UI

- Issues tab (new default) with severity grouping, per-type explanations and
  how-to-fix, drill-down to affected pages, CSV/JSON/Sheets export, and the
  'we were blocked' banner when the crawl was challenged.
- Tabs always render (Issues/Pages, Performance when Lighthouse ran);
  audit route search schema gains the issues tab and defaults to it.

Stacks on claude/audit-p0-server (issue engine + persistence).

* badseo.dev: render the badge logo as a white tree, no chip

The silver source logo was invisible on the dark pill, so it sat in a white
chip. Render it white via a CSS filter instead, so the tree fills the pill
with no backing background.

* badseo.dev: add build (typecheck) step before deploy

- Add 'build'/'typecheck' scripts (tsc --noEmit); 'deploy' now runs the build
  before wrangler deploy.
- Scope the tsconfig typecheck to the Worker source (src/); the e2e harness in
  scripts/ imports the main app and is run with tsx from the repo root.
- Document the deploy flow and first-time custom-domain setup in the README.

* badseo.dev: add trailing-slash redirect-cycle fixture + regression test

Reproduces the 508 "Loop Detected" class of bug from every-app#61: a
CMS-style page whose canonical URL ends in a trailing slash, with the non-slash
form 301-redirecting to it. A crawler that strips trailing slashes turns the
canonical /foo/ back into /foo, follows the 301 to /foo/, strips it again, and
loops.

- New fixture at /redirect/trailing-slash: the non-slash form (intercepted in
  index.ts on the raw path) 301s to the slash form, which is served as the
  canonical 200.
- Harness asserts the page is crawled exactly once as a 200 with NO redirect
  loop, plus a dedicated "Trailing-slash cycle -> 200, no loop" guard.

Verified the guard bites: temporarily disabling crawlPage's slash-canonical
inline-follow makes both checks fail (redirect-loop, status 301); with it in
place the harness is 38/38, 25/25 issue types.

* Add webapp-testing skill (installed via /reload-skills)

Vendors the anthropics/skills webapp-testing toolkit: real files under
.agents/skills/webapp-testing, a symlink from .claude/skills/, and skills-lock.json
pinning the source + hash. Matches how the other project skills are tracked.

* Site audit: redesign issues tab as grouped table + calmer page header

- Issues: single bordered table with severity sections (Critical/Warning/Info
  headers carry the counts), dot indicators instead of filled pills, plain
  right-aligned page counts, all rows collapsed by default; expanded rows get
  a severity-colored left rule
- Removed the dead severity-count chips (they looked like filters but were
  inert spans)
- Header: audited hostname is now the H1 with the status badge inline
- Blocked banner: compact tinted panel instead of a full-size alert
- Stats: hairline strip instead of four separate cards; issues stat shows a
  severity breakdown, Lighthouse tile hidden when no tests ran, dropped the
  orange issues-count coloring

* audit: fix trailing-slash redirect cycle at the root (preserve slashes)

Replaces the crawlPage inline-follow workaround with the root-cause fix, so we
don't carry two fixes for the same bug (every-app#61).

- normalizeUrl: stop stripping trailing slashes. A trailing slash is the
  canonical form on most CMSes, which 301 the non-slash version to it. Stripping
  rewrote the canonical URL into its own redirect source and looped (508). Now
  /path and /path/ are distinct and the redirect resolves normally.
- crawlPage: remove the isSelfAfterNormalization inline-follow (+ now-unused
  resolveRawUrl). With slashes preserved it's dead code; a trailing-slash
  redirect is recorded as an ordinary hop.
- add canonicalUrlKey (www/http/https-tolerant) and use it for the Lighthouse
  homepage match, which had the same redirect-mismatch vulnerability.
- tests: preserve-trailing-slash + canonicalUrlKey unit tests; badseo harness
  guard is now fix-agnostic (canonical resolves to 200, no loop/error).

Verified: 36 audit unit tests pass, tsc clean, badseo e2e 38/38. Reintroducing
stripping makes the trailing-slash guard fail (redirect-loop), confirming the
regression guard bites.

* Audit: add no-outgoing-links + meta-description-too-short checks, catch empty H1s

Two checks Ahrefs covers that we didn't, plus a fix: <h1></h1> now counts
as missing. badseo.dev gains fixtures for all three (41 checks, 27/27
issue types covered).

* Audit pages table: honest redirect/non-HTML rows, wrapped titles

- 3xx rows show their redirect target (dim →) instead of a red 'missing'
  title, and dash out H1/Words/Images since nothing was analyzed
- red 'missing' only when the engine actually flagged missing-title, so
  200 non-HTML files (security.txt) read as blank, not broken
- URL cells include the host when it differs from the audited site's, so
  apex→www redirect sources no longer render identically to their target
- titles wrap to two lines (line-clamp) in a wider column instead of
  truncating at 220px; PagesTable moved to its own file (lint max-lines)

* Audit pages table: canonical-host display, URL default sort, full title wrap

- host prefix now compares against the site's predominant 2xx host, not
  the typed start URL — auditing apex 12port.com no longer prefixes every
  www row with the host
- default sort by URL so the table opens as a site inventory instead of
  leading with redirects on error-free sites
- titles wrap fully instead of clamping at two lines; long titles are the
  thing being audited, so their tails shouldn't be hidden

* ci: exclude vendored skills from prettier; format test file

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant