Skip to content

The landing page footer links to nowhere #92

Description

@georgekaran

Turning on jsx-a11y and react in #91 pointed tabstop's linting at tabstop's own frontend. One finding is still open.

A placeholder href="#" in the footer — jsx-a11y/anchor-is-valid

web/src/screens/modules/audit/pages/Home/landing.tsx:637

{/* The footer targets do not exist yet; the placeholder keeps these
      rendering as links so the layout and focus order are final. */}
{FOOTER_LINKS.map((label) => (
  // oxlint-disable-next-line jsx-a11y/anchor-is-valid
  <Button key={label} variant="link" size="sm" render={<a href="#" />}>

href="#" is a link to nowhere. It takes focus, appears in a screen reader's link list, and moves the caret to the top of the document when activated — the failure mode of an inert control rather than a styling detail.

This is the same inert-control problem already noted informally against the landing page (theme toggle, "Try it free", "View full report", these footer links). Fixing it means deciding what those links are: real destinations, or removed until they have one. A <button> with no href would at least stop claiming to be navigation.

The linter no longer reports this. #93 added the inline disable above, so nothing fails until someone reads the source. The suppression's reasoning is defensible if the footer is deliberately provisional — but it means this issue is now the only record.

Resolved: array index in a React key — react/no-array-index-key

ViolationList/index.tsx:105 now carries the justification the acceptance asked for, so this half is closed:

{/* Audit nodes are render-once data that never reorders, and
    two nodes can share the same selector and markup, so
    content cannot make a unique key. */}

Triage: what did NOT survive

Recorded so the next person to run this does not re-investigate. Eight further findings were false positives from two causes:

Ariakit's render prop (5 findings). jsx-a11y/anchor-has-content and control-has-associated-label read render={<a href="#x" />} as an empty anchor. The element is a prop that Ariakit merges children into at runtime, so the rendered output has content and every link in the app trips both rules. Structural incompatibility, not a threshold — both are off in .oxlintrc.json with that reason recorded.

prefer-tag-over-role (2 findings). Suggests <output> in place of role="status" on AuditStatus and RouteAnnouncer. <output> means "the result of a calculation"; a route announcer is not one. Off, with the reason recorded.

Also worth recording

react/rules-of-hooks and react/exhaustive-deps are now enabled and find nothing. Neither is in oxlint's correctness category, so neither was running before. For a screen built on polling with useEffect, a clean exhaustive-deps is a real result rather than an absence of one.

Acceptance

  • the footer links no longer claim to be links to somewhere
  • the oxlint-disable-next-line jsx-a11y/anchor-is-valid in landing.tsx is gone, not moved

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:webFrontend under web/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions