WORK-301
Setting up your dashboard 0 entities found · 9/32 branches scanned
ID:WORK-301Status:done

file-ref rune — path-based file references with optional drawer preview

The new inline rune from SPEC-078 Capability 1 — a path-based reference that renders an <a> linking to a project file's canonical GitHub URL (or, with preview="drawer", hoists a drawer containing the file's snippet plus a footer link to GitHub). The third member of the registry-reference family alongside xref (entity id → link) and expand (entity id → inlined content): file-ref is the path-based sibling.

Priority:highComplexity:moderateMilestone:v0.17.0Source:SPEC-078
claude/spec-078-implementation View source

Criteria completion

Criteria completion: 10 of 10 (100%) checked; tracking started on May 29, no incremental history yet0%25%50%75%100%May 29Jun 15

Tracking started May 29 — check back for trends.

Branches 3
claude/spec-078-implementation current done
main donechangeset-release/main doneclaude/spec-078-tighten ready
History 2
  1. 5e2bc10
    Created (done)by bjornolofandersson
  2. 0ddaa3b
    Content editedby Claude
    plan: break SPEC-078 into six work items, accept the spec, slot into v0.

Acceptance Criteria

  • New rune at packages/runes/src/tags/file-ref.ts with attributes path (required string), lines (optional 42-58 or 42 shape), label (optional), preview (enum "drawer" for v1; reserved "popover" | "details" | "sidenote" for the schema's matches but rejected by the resolver).
  • Without preview: emits an inline <a> with href computed by the helper from WORK-299 (buildGithubBlobUrl). When repoUrl is missing the link emits without href and a one-time per-page build warning fires.
  • With preview="drawer": emits an inline <a href="#drawer-{slug}"> plus a hoist sentinel (per WORK-300). The hoist's payload populates the drawer body with {% snippet path=… lines=… /%} and the chrome footer with a View source on GitHub → link.
  • Label default is the filename (the path's basename) when no explicit label is set. Path with no filename falls back to the raw path. Documented in the rune description and the site docs.
  • Path sandbox reuses packages/runes/src/lib/read-file.ts: absolute paths rejected, traversal escapes (..) rejected, symlinks escaping the project root rejected, missing files error at build time. Same error messages as snippet.
  • A11y: when preview="drawer" is set, the inline <a> carries aria-controls="drawer-{slug}" and gains aria-expanded="false" (the existing drawer behavior layer flips it to "true" on open, same as the xref → drawer flow today).
  • No-JS fallback: with no JS, href="#drawer-{slug}" is a real in-page anchor that scrolls to the hoisted drawer's SSR-visible fallback (the existing drawer rune's behavior; no new fallback needed).
  • Engine config entry for FileRef in packages/runes/src/config.ts: { block: 'file-ref' }. Catalog entry via defineRune in packages/runes/src/index.ts.
  • Lumina CSS in packages/lumina/styles/runes/file-ref.css for the inline link — modest font tweak to distinguish from prose links, external-link arrow icon when the href is GitHub and preview is absent.
  • Tests in packages/runes/test/file-ref*.test.ts cover: GitHub URL build for all lines shapes; missing repoUrl warning; preview="drawer" sentinel emission; sandbox enforcement; label default vs explicit; in-page anchor href when previewing; CSS coverage selectors.

Approach

Mostly composition of pieces shipping in the prior work items — buildGithubBlobUrl (WORK-299), the hoist mechanism (WORK-300), the drawer footer slot (WORK-298), and the read-file sandbox + snippet rune (existing). The new rune is the glue.

Two transform branches:

  • No preview: emit <a> directly with GitHub URL (or no-href + warning).
  • preview="drawer": emit <a href="#drawer-{slug}"> and the hoist sentinel meta tag carrying path / lines / repoUrl context.

The hoist mechanism's payload-rendering side (turning the sentinel into a snippet body + footer link) is part of this work item — it calls {% snippet %} programmatically with the same options the snippet rune supports.

Catalog entry goes in the Registry category established in v0.16 (SPEC-078's framing: file-ref sits with xref / expand as the path-based sibling of the entity-id-based pair).

Dependencies

  • WORK-298 — drawer footer slot.
  • WORK-299repoUrl / repoBranch + URL helper.
  • WORK-300 — hoist mechanism.

References

  • SPEC-078 — Capability 1.
  • SPEC-062snippet rune; drawer body composition.

Resolution

Completed: 2026-05-29

Branch: claude/spec-078-implementation

What was done

  • packages/runes/src/tags/file-ref.ts — new self-closing rune with path (required), lines, label, preview attributes. Transform emits a sentinel meta + placeholder <a> inside a <span data-rune="file-ref"> wrapper that's inline-safe in prose. Label defaults to the path's basename when no explicit label is given.
  • packages/runes/src/file-ref-resolve.ts — postProcess resolver that walks file-ref sentinels and binds each inline <a> to its canonical GitHub URL via buildGithubBlobUrl (no-preview case) or a #drawer-{slug} fragment + a hoist sentinel (preview case). Includes the hoist builder that registers itself with the drawer pipeline at module load — reads the file via the snippet sandbox (readSnippetFile), wraps in <figure class="rf-snippet"><pre data-language="..."><code>{content}</code></pre></figure>, builds the drawer header / body / footer.
  • packages/runes/src/config.tsFileRef: { block: 'file-ref' } engine entry; resolveFileRefs wired into the postProcess chain between drawer-auto-title and the hoist pass; repoUrl / repoBranch threaded through CorePipelineHooksOptions → coreData → resolver.
  • packages/runes/src/index.tsfileRef schema imported, side-effect import of file-ref-resolve.js registers the hoist builder, defineRune catalog entry, exports for resolveFileRefs and FILE_REF_SENTINEL.
  • packages/lumina/styles/runes/file-ref.css — inline link styling (monospace font, primary-coloured underline, slightly thicker underline on hover); imported from index.css.
  • packages/runes/test/file-ref.test.ts — 12 tests covering: GitHub URL generation with line range / single line / no lines; label default to filename; missing-repoUrl one-time per-page warning; tag and SHA refs; paragraph preservation in prose; hoist sentinel emission with full payload; complete drawer rendering with snippet body + GitHub footer; footer hides when repoUrl absent; build error on missing projectRoot; sandbox enforcement; per-page dedup of repeated references.
  • Contract files (contracts/structures.json + packages/lumina/contracts/structures.json) regenerated with the new FileRef entry.

Notes

  • file-ref is a sentinel-resolved rune (like collection / aggregate) rather than a transform-only rune so that the rune doesn't need site-config access at transform time. The transform just stashes the path / lines / label / preview attrs as metas; the resolver in postProcess binds the actual href and emits the hoist sentinel.
  • For the preview="drawer" no-JS case the inline <a href="#drawer-{slug}"> is a real in-page anchor. The hoist mechanism (WORK-300) emits a real <section class="rf-drawer"> at the page root with id="drawer-{slug}" so the anchor resolves to it — readers without JS scroll to the visible block fallback the section produces.
  • The hoist builder fails gracefully when projectRoot isn't threaded (build error, no drawer emitted) or when the path escapes the sandbox (SnippetSandboxError surfaced as a build error). The inline anchor still works in both cases — the resolver's branch that emits the anchor runs before the hoist builder gets a chance to fail.
  • 12 file-ref tests + 15 hoist tests + 22 drawer tests + 14 github-url tests = 63 new tests across the WORK-298..301 series. Full suite 1405/1405 green.