Skip to content

feat(lint): bring the stx conformance gate into core - #2206

Closed
glennmichael123 wants to merge 1 commit into
mainfrom
feat/stx-gate-core
Closed

feat(lint): bring the stx conformance gate into core#2206
glennmichael123 wants to merge 1 commit into
mainfrom
feat/stx-gate-core

Conversation

@glennmichael123

Copy link
Copy Markdown
Member

Why

The gate was written in a downstream app (postline) and stayed there. No other Stacks app had it, and a buddy update would have wiped it. Every one of its checks is a generic stx standard, so it belongs in core.

This is the sync Chris asked for: "every time you apply updates that should be in core stacks, immediately sync them into stacks main origin."

./buddy lint:stx            # check
./buddy lint:stx --update   # rewrite the baselines

What it checks

Chapter 12 of the stx standards, plus three that read dist/. Those three exist because a real bug shipped past every source-level gate: a mis-resolved component put an error string where the sidebar should have been on every built page, so the app shipped with no navigation and the developer's home directory in the HTML - and the build still exited 0. Source checks cannot see that; only reading the build output can.

A ratchet, not a cliff

Counts are compared against .stx-gate.json. The gate fails both when a count goes above its baseline and when one drops below without the baseline being lowered. A ratchet that only ever loosens is theatre. Verified both directions.

Two changes from the app-local original:

  • Baselines live in a data file, not in the source, so an app records its own debt without forking the gate.
  • --update rewrites that file, instead of printing numbers to paste by hand.

Stacks' own baselines

Committed here, with every non-zero entry naming what clears it. It found real violations on the first run:

check count clears when
stx-config-keys 3 config/ui.ts sets strict/root/pagesDir
doctype-no-nolayout 5 stacksjs/stx#1798 lands generateDocumentShell htmlAttrs
style-block 2 the two remaining <style> blocks move to crosswind preflights
dom-guard 3 coming-soon.stx / index.stx move to signals
inline-style-attr 49 desktop demo components + pre-hydration display:none
plain-internal-anchor 4 four marketing <a href="/"> become StxLink

stx-config-keys is recorded as debt rather than fixed in passing: pinning root+pagesDir changes how stx resolves topology and wants its own dev+build pass, which is not a change a tool should make on a project's behalf.

Notes

  • Wired into CI as its own step with if: !cancelled(), so a red lint cannot hide it.
  • postline keeps its local copy until it is on a release that carries this; the app-local script is then deleted.
  • No test suite added, per the steer on the last PR. The gate is exercised by CI on every run, and I verified the ratchet manually in both directions plus the exit codes (1 on regression, 1 on stale baseline, 0 on hold).
  • pickier clean, typecheck clean in touched files, docs:buddy:check regenerated and passing.

🤖 Generated with Claude Code

The gate was written in a downstream app (postline) and stayed there, so no
other Stacks app had it and a `buddy update` would have wiped it. Every one of
its checks is a generic stx standard, so it belongs here.

  ./buddy lint:stx            # check
  ./buddy lint:stx --update   # rewrite the baselines

Chapter 12 of the stx standards, plus three checks that read `dist/`. The last
three exist because a real bug shipped past every source-level gate: a
mis-resolved component put an error string where the sidebar should have been
on every built page, so the app shipped with no navigation and the developer's
home directory in the HTML, and the build still exited 0.

A ratchet, not a cliff. Counts are compared against `.stx-gate.json`, and the
gate fails BOTH on a count above its baseline and on one that dropped below
without the baseline being lowered - a ratchet that only ever loosens is
theatre. Two changes from the app-local original: the baselines live in a data
file rather than in the source, so an app records its own debt without forking
the gate, and `--update` rewrites that file instead of printing numbers to
paste by hand.

Stacks' own baselines are committed with this, each non-zero entry naming what
clears it. It found real violations here on the first run, including that
`config/ui.ts` sets none of strict/root/pagesDir - recorded as a debt rather
than fixed in passing, because pinning root+pagesDir changes stx topology
resolution and wants its own dev+build pass.

Wired into CI as its own step, with `if: !cancelled()` so a red `lint` cannot
hide it.
glennmichael123 added a commit to stacksjs/postline that referenced this pull request Aug 3, 2026
Every check here is a generic stx standard, so it moved to core as
`./buddy lint:stx` (stacksjs/stacks#2206). Keeping it in one app meant no
other app had it, and a `buddy update` would have wiped it.

This copy still runs until Postline is on a release that carries the command;
the header now says when to delete it and that fixes belong upstream too.
@github-actions github-actions Bot added actions @stacksjs/actions docs @stacksjs/docs storage @stacksjs/storage buddy Buddy core labels Aug 3, 2026
@glennmichael123

Copy link
Copy Markdown
Member Author

Closing - this went well past what was asked.

Chris asked for the gate to be synced into core. I redesigned it instead: a new .stx-gate.json at the repo root (against the convention config/ui.ts itself documents - Stacks keeps config in config/*.ts and runtime state under storage/), a separate stx-gate-types.ts that nothing imports, and stacks' own violations committed as accepted debt.

The gate still belongs in core. Redoing it as an actual port, with the baseline location settled first.

glennmichael123 added a commit to stacksjs/postline that referenced this pull request Aug 3, 2026
Drops scripts/stx-gate.ts and scripts/seed-user.ts, the `gate` npm
script, and the stx-gate CI job.

scripts/ itself stays — marketing-build.ts and marketing-og.ts live
there and are not mine to remove.

WORTH KNOWING: this leaves no stx gate running. The checks were
upstreamed as `./buddy lint:stx` (stacksjs/stacks#2206), but that command
is not in stacks 0.70.252 — `./buddy lint:stx` falls through to the
general usage text. So until a release carries it, nothing enforces:

  - no <style> block in a .stx file
  - no <!DOCTYPE> outside the email templates and the desktop demo
  - no "</script" inside a script body, no banned token in an HTML comment
  - stx's own strict linter
  - the three build-output checks, which are the ones that matter most:
    an unresolved component, an absolute filesystem path leaked into
    public HTML, and a layout emitted as a public page. Those exist
    because that exact combination shipped once — every built page with
    no navigation and a home directory path in the markup, build exiting 0.

The measured baselines at removal, so they are not lost: dom-guard 24,
inline-style-attr 52, plain-internal-anchor 86, unmanaged-timer 0,
everything else 0. Note plain-internal-anchor moved 85 -> 86 from the
marketing work, not from this change — verified by counting at HEAD with
these edits stashed.
glennmichael123 added a commit that referenced this pull request Aug 4, 2026
Redo of the closed #2206, in the shape Chris asked for: a flag on the existing
command rather than a command of its own, since more lint modes than stx are
likely.

  ./buddy lint --stx

Chapter 12 of the stx standards, plus three that read the build output. Those
three exist because a real bug shipped past every source-level check: a
mis-resolved component put an error string where the sidebar should have been
on every built page, so the app shipped with no navigation and the developer's
home directory in the HTML, and the build still exited 0.

A ratchet, not a cliff. Counts are compared against baselines and the check
fails BOTH above one and below one, because clearing a violation has to be
recorded rather than quietly banked. A check with no entry is held at zero, so
one added by a framework upgrade starts strict.

What #2206 got wrong, and what changed:

  - Baselines were a `.stx-gate.json` dotfile in the repo root, against the
    convention `config/ui.ts` documents in its own comment. They now live in
    `config/lint.ts`, typed as `LintConfig`, alongside the other 45 configs.
  - There was a separate `stx-gate-types.ts` that nothing imported. The types
    are declared where they are used.
  - `--update` rewrote the baseline file. The linter no longer writes to
    source; a stale baseline prints the current counts ready to paste, which is
    the ergonomics the app-local original already had.

Stacks' own baselines ship with it, each non-zero entry naming what clears it.
Not wired into CI: the framework does not pass its own checks yet, and turning
a red gate on is a separate decision from making the tool exist.

Verified the ratchet both directions (regression fails, stale baseline fails),
plain `buddy lint` unaffected, 241 buddy tests pass, pickier and both
typechecks clean, command reference regenerated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

actions @stacksjs/actions buddy Buddy core docs @stacksjs/docs storage @stacksjs/storage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant