GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — docs: give every table header cell text and fail lint on empty ones by nickvigilante · Pull Request #30879 · coder/coder

Conversation

@nickvigilante

@nickvigilante nickvigilante commented Oct 15, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Summary

axe reports empty-table-header on 116 generated CLI reference pages and several hand-written pages: a header cell with no text is announced by screen readers as an unnamed column. This PR gives every table header cell in docs/ text, and adds a lint check so empty header cells can't come back.

How to review this PR

  • docs/reference/cli/: 116 files, these all generate identically, so I would only spot check 2-3 of them and skip the rest
  • docs/* outside of docs/reference/cli/: 6 files, normal review of these
  • scripts: 5 files, code review of the Bash and Go files to make sure we're properly inserting headers into each table

What changed

Generator. scripts/clidocgen wrote an empty header row (| | |) over each option's metadata table (Type, Environment, YAML, Default). It now writes | Property | Value |. Subcommand tables already had Name | Purpose and are unchanged. Regenerated with make docs/reference/cli/index.md: 116 files under docs/reference/cli/ change. The header rows change, and the body rows of each option table are re-padded to the wider first column; the content is otherwise identical. Every option on those 116 pages now shows a visible Property | Value header row above Type, Environment, YAML, and Default, so this is a visual change on the docs site as well as a screen reader one. The CLI's own --help output doesn't use this template, so it's unchanged.

Audit logs. docs/admin/security/audit-logs.md already has Field | Tracked headers on main (the generator writes them), so there was nothing to change there; the live-site finding predates that.

Hand-written tables.

  • docs/admin/networking/port-forwarding.md: the CORS matrix had three empty spanning corner cells. The corner now reads From user and From workspace (spanning the three row-header columns, beside the Alice/Bob and Workspace rows) and To user, To workspace, To app (over the three row-header columns). The grid is still 3 header rows by 7 columns. The From cells have scope="row" and the other header-row cells scope="col", so the From labels don't become column headers for the body's label columns; the body's user, workspace, and app labels are <th scope="row">; and a <caption> says columns are the origin and rows the destination.
  • groups-roles.md, airgap.md, scale-testing.md, workspace-tags.md: these four corner cells are already filled by docs: give screenshots their own alt text and fill empty table headers #30873. The new check would fail CI without them, so this PR carries the same four lines, byte-identical to docs: give screenshots their own alt text and fill empty table headers #30873 (Permission, Component, Scenario, Input), so whichever PR merges second rebases without conflict. If docs: give screenshots their own alt text and fill empty table headers #30873 merges first, these hunks drop out.

New check. scripts/docshtmlcheck (make lint/docs-html, part of make lint) now also reports empty-table-header. I put it there because that checker already parses every Markdown file under docs/, generated pages included, with goldmark and tokenizes raw HTML with golang.org/x/net/html, and it already runs in CI. A markdownlint custom rule would have needed a new JS rule and HTML parsing there.

  • Markdown pipe tables: the header row, via goldmark's GFM table extension.
  • Raw HTML tables: every <th>, tracked across HTML blocks, with entities decoded and whitespace trimmed, so <th></th>, <th>&nbsp;</th>, and <th><br></th> are all reported. Markdown between the HTML blocks of an open <th> counts as its text. Tables in code blocks are ignored.
  • Output: docs/x.md:29: empty-table-header: header cell 1 has no text (table's first header "Auditor"), plus a summary footer.
  • The tokenizer loop moved out of scanNode into a shared tokenizeSegments helper so both checks use it; scanNode's behavior is unchanged.
  • The style guide's checks table gets one row; the README for the checker describes the rule.

Testing

  • Tests first: TestCheckTables (now 22 cases) and TestRunEmptyTableHeader failed against a stub, then pass. The existing docshtmlcheck tests pass unchanged.
  • go run ./scripts/docshtmlcheck on main reports 1,561 empty header cells (116 CLI pages, port-forwarding, and the four docs: give screenshots their own alt text and fill empty table headers #30873 tables); on this branch it reports none.
  • go test ./scripts/docshtmlcheck/ ./scripts/clidocgen/ ./scripts/docgenenv/, golangci-lint on the changed packages, make lint/docs-html lint/docs-manifest lint/docs-gen-filter lint/emdash, pnpm run check-docs, and Vale on the changed hand-written pages all pass.
  • make gen couldn't run to completion locally (no protoc-gen-go); CI's gen check covers it. The CLI pages were regenerated with the repo's own target.

Review feedback applied in cfd29a2 (entities decoded in Markdown text, image alt text and autolinks count as text), 709a58f (per-kind counts, finding order, CLI generator note), 8e61ff3 (CORS matrix scopes and caption), and b807bcb and 2871f89 (style guide rule section, make lint/docs-html in the checks table, coverage counts).

Linear: DOCS-1308


🤖 Built with AI assistance.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@nickvigilante
docs: give every table header cell text and fail lint on empty ones
2ee96f8
Screen readers announce a table header cell with no text as an unnamed
column, and axe reports it as empty-table-header.

- clidocgen writes "Property | Value" over each option's metadata table
  instead of an empty header row; the CLI reference is regenerated.
- The port-forwarding CORS matrix labels its corner cells (From user,
  From workspace, To user, To workspace, To app).
- Four Markdown matrices get a corner label, byte-identical to #30873.
- docshtmlcheck (make lint/docs-html) now fails on empty header cells in
  Markdown pipe tables and raw HTML tables, naming the file, line, and
  the table's first header.

Assisted-by: AI
@github-actions

github-actions Bot commented Oct 15, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

  • docs/admin/networking/port-forwarding.md
  • docs/admin/templates/extending-templates/workspace-tags.md
  • docs/admin/users/groups-roles.md
  • docs/install/prepare/airgap.md
  • docs/install/validate/scale-testing.md
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot assigned nickvigilante Oct 15, 2026
@nickvigilante
fix(scripts/docshtmlcheck): walk the table AST without an error path
a49a404
errcheck and revive flagged the discarded ast.Walk and strings.Builder
errors in the table check. Neither visitor can fail, so walk the tree
with a small pre-order helper instead.

Assisted-by: AI
@nickvigilante
nickvigilante marked this pull request as ready for review October 16, 2026 13:15
@nickvigilante
Merge branch 'main' into vigilante/docs-table-headers
44a96aa
@nickvigilante
nickvigilante requested a review from Jesswinters October 16, 2026 13:25
@nickvigilante
docs(reference/cli): regenerate secret list with the option table header
4933a37
The main merge brought in the new secret list page, generated before the
Property | Value header, so the merge left its --column table empty.

Assisted-by: AI
@nickvigilante nickvigilante mentioned this pull request Oct 16, 2026
feat: own the docs accessibility checks the axe Linter doesn't run on Markdown #30905
Draft
Jesswinters
Jesswinters reviewed Oct 16, 2026
View reviewed changes

@Jesswinters Jesswinters left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is right: fixing the generator and gating the result in docshtmlcheck, which already parses every page. I'm commenting rather than approving because of two issues, both inline. The rewritten port-forwarding table gives screen readers contradictory headers, and the new check misses &nbsp; in Markdown cells.

What I verified:

  • The new gate catches the problem. docshtmlcheck reports nothing on this branch and 1,559 empty header cells on the merge base (the body says 1,555). A test merge with current main is also clean, so no newly added CLI page breaks the gate.
  • CLI pages: all 115 change by 1,444 lines added and 1,444 removed. After mapping Property | Value back to | | | and collapsing padding, every file is identical to main, so content is unchanged. The body could mention that body rows are re-padded too, not just header rows.
  • scanNode refactor: I built main's checker and this one and ran both over all of docs/ plus a file of HTML errors. The non-table output is byte-identical.
  • #30873 overlap: #30873 is still open, and the four shared hunks are byte-identical to it.
  • Local runs: go test, go vet and golangci-lint on scripts/docshtmlcheck and scripts/clidocgen all pass.
  • CI: gen and lint pass; gen regenerated the CLI docs with no diff, which covers the make gen caveat. The two failures are unrelated:
    • Storybook fails on userengagementchart--loaded, the same as the last four main runs. This PR touches no site/ files.
    • Documentation Updates timed out after 2400 s waiting for its agent.

One thing to acknowledge in the PR: every option on 115 pages now shows a visible Property | Value header row. That's a visual change on the docs site.


Generated by Coder Agents on behalf of @Jesswinters.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread docs/admin/networking/port-forwarding.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread scripts/docshtmlcheck/tables.go Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread scripts/docshtmlcheck/tables.go Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread scripts/docshtmlcheck/main.go Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread docs/.style/style-guide/README.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

nickvigilante added 5 commits October 16, 2026 15:21
@nickvigilante
fix(scripts/docshtmlcheck): decode entities and count alt text in tab…
cfd29a2
…le headers

goldmark keeps an entity such as &nbsp; as literal source text until it
renders, so a pipe header cell or the Markdown between <th> blocks holding
only &nbsp; looked non-empty and passed, while axe still reports it because
U+00A0 trims to nothing. Text nodes are now run through UnescapeString,
except inside a code span, where the entity renders literally and is real
text.

axe's has-visible-text also counts an image's alt text, so an <img alt> in a
<th> or a pipe header cell, and an autolink or code block between <th>
blocks, no longer read as empty. A table nested inside a <th> still does, and
the README lists that under Limitations. The README intro and "How it works"
now describe the table check too.

Assisted-by: AI
@nickvigilante
refactor(scripts/docshtmlcheck): count findings per kind and route CL…
709a58f
…I notes to the template

run derived the HTML count as reportFindings' total minus the table
findings, which only worked because reportFindings lumped table findings in
with HTML ones, and its doc comment no longer said what it returned.
reportFindings now returns a count per kind.

Sorting the merged findings by line also moved line-0 stale-allowlist
findings ahead of the content findings, where they used to come last; they
sort last again.

On a CLI reference page the generated-page note sent authors to codersdk
and swagger, but a CLI page comes from --help text and
scripts/clidocgen/command.tpl, where an empty option-table header would be
fixed. The note now names those for CLI pages.

Assisted-by: AI
@nickvigilante
docs(admin/networking): scope the CORS matrix headers and caption the…
8e61ff3
… table

Without a scope, a <th> in a row with no data cells is a column header, so
From user and From workspace, which span the first three columns, became
column headers for the body's label columns alongside To user, To workspace
and To app. A screen reader on "Alice" announced "To user, From workspace,
From user", which contradicts itself.

The From cells now have scope="row" and the other header-row cells
scope="col", so each label stays on its own header row. The body's user,
workspace and app labels become row headers, so each check or cross mark is
announced with both its destination and its origin. The third header row
puts App A to App D beside the To labels with nothing saying they are the
origin apps, so a caption states that columns are the origin and rows the
destination, matching the From and To labels the table already uses.

Assisted-by: AI
@nickvigilante
feat(scripts/styleclaims): count scripts/docshtmlcheck as a tool cita…
b807bcb
…tion

A style guide section enforced by the docs HTML checker had no way to say
so: styleclaims only read markdownlint, MD rules, Vale rules and
scripts/check_emdash.sh as tool citations, so a footer naming
scripts/docshtmlcheck classified as documentation-only and the coverage
table could not count it.

Assisted-by: AI
@nickvigilante
docs: give the empty-table-header check a rule section and count it
2871f89
The checks table row for empty-table-header pointed at no rule section,
because the formatting guide's Tables section was documentation-only, and
the counts below the table still said 9 checked rules. Tables now states the
header-text rule with an enforcement footer citing scripts/docshtmlcheck,
and the counts move to 10 checked and 45 documentation-only.

The row's Tool column now names make lint/docs-html, matching the
make lint/emdash row, the paragraph on which checks fail the build includes
it, and the Makefile comment and echo for lint/docs-html mention table
headers.

Assisted-by: AI
Jesswinters
Jesswinters reviewed Oct 16, 2026
View reviewed changes

@Jesswinters Jesswinters left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 2871f89. Every point from my first review is fixed, and I checked each by running it. I'd approve the code. I'm commenting rather than approving only because the branch now conflicts with main, so no ci.yaml job (gen, lint, lint-docs, Storybook) has run on this head. After a rebase, main also has a page that would fail the new check (details below).

Fixed:

  • Entity decoding. | &nbsp; |, &#160;, &#xA0; and <th> + &nbsp; between blocks are now reported. A code-span `&nbsp;`, \&nbsp; and &amp;nbsp; are correctly not reported.
  • Image alt text. Alt text counts in every form: HTML, uppercase, self-closing, multi-line, pipe-cell and ![Logo](x). alt="", alt=" ", alt="&nbsp;" and a missing alt are still reported as empty.
  • Text between <th> blocks. An autolink or fenced code between <th> blocks counts as text. The nested-table case is documented under Limitations.
  • Checker output. issueCounts reports per kind, stale-allowlist findings sort last, and the note on CLI pages now points at scripts/clidocgen/command.tpl.
  • Port-forwarding table. By the HTML header algorithm, and confirmed with axe's own getHeaders, "Alice" now gets only "To user". The ✅/❌ cells get both column and row headers. <th scope="row"> in thead is valid, and it's what stops the "From" cells being treated as column headers. axe 4.13 passes empty-table-header, td-has-header, th-has-data-cells, scope-attr-valid and td-headers-attr. The caption matches the layout.
  • Style guide and styleclaims. The Tables rule, footer, Tool column, Makefile and README wording are updated. The styleclaims change is needed (without it, the new footer wouldn't count as a tool citation), it mirrors how check_emdash.sh is handled, and it has tests.

Local runs: go test, go vet and golangci-lint on docshtmlcheck, clidocgen and styleclaims; go run ./scripts/docshtmlcheck (exit 0); go run ./scripts/styleclaims (exit 0); Vale, markdownlint and the table formatter on the changed docs. All clean. I also ran 32 adversarial inputs against the checker, and each matched axe or the documented limits.

Before merge (rebase):

  1. README counts. #30782 changed the same lines in docs/.style/style-guide/README.md: 88 rules, the coverage table, and the line 92 sentence. I resolved the conflict in a scratch copy, and these values pass styleclaims:
    • The rules count is 88: "checks 10 of them, and 1 of those 10", "The other 78 … 79 on published pages".
    • The Formatting row is 13 | 3 | 0 | 10, and Total is 88 | 10 | 34 | 44.
    • The line 92 sentence becomes "So the 8 rows in the table above name 11 checks, because the markdownlint row bundles 4, and those 11 checks cover 10 sections".
  2. New CLI page. main added docs/reference/cli/ai-gateway/keys/create.md with the old | | | header (line 21). Please re-run make docs/reference/cli/index.md after rebasing, or lint/docs-html and gen will fail.

#30873 is still open, and git merge-tree shows it merges cleanly with this branch in either order. One optional wording nit is inline.


Generated by Coder Agents on behalf of @Jesswinters.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread docs/admin/networking/port-forwarding.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

@nickvigilante
Merge remote-tracking branch 'origin/main' into vigilante/docs-table-…
4a7913c
…headers

# Conflicts:
#	docs/.style/style-guide/README.md
@coderagents

coderagents Bot commented Oct 20, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/.style/style-guide/README.md - The sentence under the checks table ("So the 7 rows in the table above name 10 checks, because the markdownlint row bundles 4, and those 10 checks cover 9 sections") is now stale. This PR adds an 8th row, empty-table-header, to that table, so the table now has 8 rows and names 11 checks (counting the 4 markdownlint rules individually, the same way the sentence already does). Update the count to match. (Addressed in 3002907; a later main merge also brought the docsmanifestgen row, so the sentence now reads "9 rows ... 12 checks".)

Automated review via Coder Agents

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nickvigilante
nickvigilante requested a review from Jesswinters October 20, 2026 18:09
Jesswinters
Jesswinters approved these changes Oct 20, 2026
View reviewed changes

@Jesswinters Jesswinters left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 4a7913cb3a, the merge of main that cleared the conflict from my last review. Approving: I found no blockers.

The merge resolution is correct. The only real conflict was the style guide's coverage counts, and the resolved table adds up: 88 rules, 10 tool-checked, 34 planned and 44 documentation-only, with the prose saying 78 by reading (79 on published pages), which matches. The other overlapping files merged cleanly, and the CORS matrix fix and the four corner cells shared with #30873 are intact. The one CLI page main changed (ai-gateway/keys/create.md) carries the new Property | Value header, and the gen job confirms it matches the generator.

Every CI job passes on this head, including gen, lint, lint-docs, check-docs, offlinedocs and required. Locally, go test passes for scripts/docshtmlcheck, scripts/clidocgen and scripts/styleclaims. go run ./scripts/docshtmlcheck and go run ./scripts/styleclaims both exit 0 on the merged tree, so nothing main brought in has an empty header cell.

There is one non-blocking nit, inline.

Generated by Coder Agents on behalf of @Jesswinters.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread docs/.style/style-guide/README.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

nickvigilante added 3 commits October 21, 2026 10:24
@nickvigilante
docs: update style guide check counts for the table header check
3002907
Assisted-by: AI
@nickvigilante
Merge remote-tracking branch 'origin/main' into vigilante/docs-table-…
2984969
…headers

# Conflicts:
#	docs/.style/style-guide/README.md
#	scripts/styleclaims/main.go
#	scripts/styleclaims/main_test.go
@nickvigilante
chore: remove stray backup file
fbb232d
Assisted-by: AI
@nickvigilante
nickvigilante enabled auto-merge (squash) October 21, 2026 17:39
@nickvigilante
docs: reword the CORS matrix caption
e3854fe
Assisted-by: AI
@nickvigilante
nickvigilante merged commit 6e51c66 into main Oct 21, 2026
32 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@nickvigilante
nickvigilante deleted the vigilante/docs-table-headers branch October 21, 2026 17:58
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@Jesswinters Jesswinters Jesswinters approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@nickvigilante nickvigilante

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@nickvigilante @Jesswinters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.