Commit log

25d5302 feat(web): style Markdown code blocks with border and tinted background

Click to expand commit body
Add subtle border, bg-muted/40 background, and rounded corners to
fenced code blocks. Reset inline code styles inside highlighted
blocks so Shiki colors show through cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

18ca38f fix(web): pre-load common languages in shared Shiki highlighter

Click to expand commit body
The rehype plugin needs languages already loaded (it's synchronous).
Pre-load 16 common languages (JS/TS, Go, Python, Rust, etc.) so
Markdown code blocks get highlighted. FileViewer continues to load
additional languages on demand.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

d52ca8b feat(web): add Shiki syntax highlighting to Markdown code blocks

Click to expand commit body
Share the Shiki highlighter between FileViewer and Markdown via a
singleton in src/lib/shiki.ts. The Markdown component uses
@shikijs/rehype/core with a pre-resolved highlighter (react-markdown
doesn't support async plugins).

The highlighter loads in a useEffect — Markdown renders without
highlighting initially, then re-renders with syntax colors once
the highlighter is ready.

Also update sanitize schema to allow Shiki's style/class attributes
on pre/code/span elements, and adjust prose styles so inline code
inside highlighted blocks doesn't get bg-muted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

597e01a fix(web): don't apply Shiki background-color to token spans

Click to expand commit body
Only the root .shiki element gets background-color. Token spans only
get color/font-style/font-weight/text-decoration. This prevents
white blocks showing through the yellow line highlight.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

a171a0f fix(web): make line number gutter background transparent

Click to expand commit body
The Shiki global CSS rule '.shiki span { background-color: ... }'
was applying to the gutter span, showing a white block through the
yellow line highlight. Force transparent background on .line-number.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

075f73d fix(web): only allow line selection via gutter click

Click to expand commit body
Replace ::before pseudo-element with an actual <span> for line numbers,
injected by the Shiki transformer. Only the gutter span has
data-line-number, so clicking code text no longer triggers selection.
Gutter gets a hover state (opacity 0.4 → 0.8) for affordance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

f24aa74 refactor(web): use Shiki defaultColor:false for CSS-only theming

Click to expand commit body
Shiki now outputs only CSS variables (--shiki-light, --shiki-dark)
on each span with no inline color/background. The CSS in index.css
maps the variables to the active theme — no !important needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

b35e620 refactor(web): replace :global(.line) with CSS module scoped class

Click to expand commit body
Use a Shiki transformer to replace the default "line" class with our
CSS module's scoped .line class. No more :global() needed — everything
is properly module-scoped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

f29d46b fix(web): use :global() for Shiki's .line class in CSS module

Click to expand commit body
CSS modules were mangling .line in selectors like
.code-content code > .line, but .line is generated by Shiki and
must stay as-is. Wrap with :global(.line) so the selector targets
the actual DOM class.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

aa39076 fix(web): use kebab-case CSS classes and Tailwind font-mono var

Click to expand commit body
Rename code_block → code-block, code_content → code-content.
Use var(--font-mono) from the Tailwind theme instead of hardcoded
font-family stack.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

4b8cb70 refactor(web): move FileViewer code styles to CSS module

Click to expand commit body
Extract the wall of Tailwind arbitrary selectors into
file-viewer.module.css with snake_case class names. The scoped
<style> tag for line highlighting uses the module's generated
class name for proper scoping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

fd059a0 fix(web): fix code block background stripes and dark mode highlight

Click to expand commit body
- Remove !bg-transparent override — let Shiki control the background
  so there's no mismatch between padding areas and line backgrounds
- Move vertical padding from <code> to first/last .line elements so
  every visible pixel has Shiki's background
- Use separate light/dark highlight colors (yellow 30% light, 15% dark)
- Fix line number opacity to work with both themes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

0ff322c fix(web): fix line highlight full-width and visibility

Click to expand commit body
- Switch lines from table-row to block display so highlight
  background spans the full container width
- Use GitHub-style yellow highlight (rgba(255,235,59,0.25))
  instead of subtle accent color
- Fix line count (was showing 0 — use text split instead of
  hast tree traversal)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

c485a03 fix(web): preserve newlines in code copy-paste

Click to expand commit body
Append a \n text node inside each .line span so copy-pasting from
the code block includes proper line breaks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

a5b0f3d fix(web): fix FileViewer line rendering issues

Click to expand commit body
- Use Shiki transformer to inject data-line-number on each .line span
  and strip whitespace text nodes between lines (caused empty rows
  with table-row display)
- Render line numbers via CSS ::before + content:attr(data-line-number)
  so gutter is always in sync with code lines
- Count lines from hast tree instead of splitting text by \n
- Use event delegation on the code block for line click handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

e82762c feat(web): add line numbers with GitHub-style line highlighting

Click to expand commit body
Replace dangerouslySetInnerHTML with native React rendering via
Shiki's codeToHast → hast-util-to-jsx-runtime pipeline.

Features:
- Clickable line numbers in a sticky gutter
- Click to select a line (#L12 in URL hash)
- Shift+click to select a range (#L12:25)
- Selected lines highlighted with accent background
- URL hash syncs both ways (navigation + popstate)
- Scrolls to selected line on initial load

Also fix dark mode theme sync — add CSS rule to swap Shiki's
--shiki-dark variables when .dark class is active.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

884afdf refactor(web): replace highlight.js with Shiki for code highlighting

Click to expand commit body
Shiki uses VS Code's TextMate grammar engine (Oniguruma WASM) for
accurate syntax highlighting that matches what developers see in
their editor.

- Lazy singleton highlighter with on-demand language loading
- github-light/github-dark dual themes with automatic dark mode
- Inline styles — no external CSS needed (removed hljs theme + overrides)
- Fine-grained imports via @shikijs/langs and @shikijs/themes

Skip FileViewer from Vitest browser tests (Shiki WASM doesn't load
in that context). Snapshot tests still cover it via happy-dom.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

c7a2f42 fix(web): add proper disabled state to Pagination Previous/Next

Click to expand commit body
Apply opacity-50, pointer-events-none, aria-disabled, and tabIndex=-1
when disabled. Prevents navigation and gives visual feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

dcaecd8 fix(web): constrain search icon size in QueryInput

Click to expand commit body
The Icon wrapper's size-4 didn't constrain the child SVG.
Add [&>svg]:size-4 to force the icon to 16px.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

8d2cf62 docs(web): update README for current architecture

Click to expand commit body
Reflect the component layer system (ui/shared/bugs/code), colocated
GraphQL fragments, Storybook + Vitest testing setup, a11y testing,
and all current tooling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

c0e6846 feat(web): add interaction tests with play functions

Click to expand commit body
Add play functions to stories for behavior testing:
- QueryInput: type "label:" → suggestions appear → Enter selects first
- WritePreview: click Preview → content switches → click Write → back

These run as real browser tests via the Storybook Vitest addon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

d4c58b6 refactor(web): migrate StatusTabs into issues list and user profile

Click to expand commit body
Replace ~45 lines of inline status toggle code in each consumer with
the StatusTabs composition component. Indicators accept an `active`
prop for explicit state control (needed because active state comes
from query parsing, not router URL matching).

Also remove unused cn/CircleDot/CircleCheck imports from issues list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

54189d4 refactor(web): move IssueFilters to shared/

Click to expand commit body
IssueFilters has zero GraphQL dependencies — it's purely presentational.
Belongs in shared/ with other reusable, domain-aware components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

571ee43 refactor(web): extract query utilities to src/lib/query-utils.ts

Click to expand commit body
Move tokenizeQuery, parseQueryString, buildBaseQuery, buildQueryString,
SortValue, StatusFilter, and SORT_OPTIONS from the route file and
issue-filters into a shared module. These are pure functions with no
React deps — removes ~80 lines from the issues route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

52bf712 refactor(web): standardize all component files to kebab-case

Click to expand commit body
Rename all PascalCase component files to kebab-case across the
codebase for consistency with the ui/ (shadcn) convention:

- shared/: IssueRow→issue-row, LabelBadge→label-badge, StatusBadge→status-badge
- bugs/: CommentBox→comment-box, IssueFilters→issue-filters, etc.
- code/: CodeBreadcrumb→code-breadcrumb, FileTree→file-tree, etc.
- content/: Markdown→markdown
- layout/: Header→header, Shell→shell

All imports updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

f22cab2 refactor(web): reorganize component hierarchy and fix shadcn config

Click to expand commit body
Restructure components into three clear layers:

  ui/       — generic shadcn primitives (button, input, avatar, etc.)
              Managed by shadcn CLI. No domain knowledge.

  shared/   — app-level reusable components with domain awareness but
              no data fetching. Typed against GraphQL fragments.
              (IssueRow, LabelBadge, StatusBadge, CommentCard,
              Pagination, QueryInput, StatusTabs, WritePreview,
              EmptyState, SectionHeading)

  bugs/     — feature components with GraphQL mutations and auth
              (CommentBox, Timeline, TitleEditor, LabelEditor,
              IssueFilters)

Also update components.json for Tailwind v4 (drop tailwind.config
reference, set config to empty string per shadcn docs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

10e6081 refactor(web): type components against GraphQL fragment types

Click to expand commit body
- LabelBadge: props typed as LabelFieldsFragment (spread fragment
  data directly onto the component)
- CommentCard.AuthorAvatar: props typed as Pick<IdentitySummaryFragment>
  with avatarUrl/displayName fields
- IssueRow stories: mock data typed as BugSummaryFragment
- CommentCard stories: mock data typed as IdentitySummaryFragment
- Update Timeline and CommentBox for new AuthorAvatar prop names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

d163600 refactor(web): introduce colocated GraphQL fragments

Click to expand commit body
Define reusable fragments next to the components that consume them:
- IdentitySummary: id, humanId, displayName, avatarUrl
- LabelFields: name, color { R G B }
- BugSummary: composes IdentitySummary + LabelFields for bug list nodes
- Timeline fragments: BugCreateCommentFields, BugAddCommentFields,
  LabelChangeFields, StatusChangeFields, TitleChangeFields

Rewrite BugList, BugDetail, and UserProfile queries to compose
fragments instead of duplicating field selections. Codegen now
includes src/components/**/*.graphql for fragment discovery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

6124a94 fix(web): make FileViewer skeleton widths deterministic

Click to expand commit body
Replace Math.random() with a deterministic formula so snapshot tests
don't flake on every run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

4a56564 refactor(web): migrate consumers to EmptyState, SectionHeading, Pagination, CommentCard

Click to expand commit body
- issues/index.tsx: EmptyState + Pagination (remove ButtonLink, ChevronLeft/Right)
- user/$id.tsx: EmptyState + Pagination (remove ButtonLink, ChevronLeft/Right)
- issues/$id.tsx: EmptyState + SectionHeading
- LabelEditor.tsx: SectionHeading
- Timeline.tsx: CommentCard (Root/AuthorAvatar/Card/CardHeader/CardBody)
- CommentBox.tsx: CommentCard (Root/AuthorAvatar/Card)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

5716452 feat(web): add EmptyState, SectionHeading, Pagination, CommentCard components

Click to expand commit body
Four new shared UI components with composition APIs:
- EmptyState: styled "no results" message
- SectionHeading: uppercase sidebar/section heading
- Pagination: createLink-wrapped Previous/Next with Info
- CommentCard: avatar + bordered card (Root/AuthorAvatar/Card/CardHeader/CardBody)

Each has stories and snapshot tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

3676d2e feat(web): add snapshot tests for all story files

Click to expand commit body
Every story file now has a corresponding snapshot test using the
portable stories API. The pattern auto-generates a test per exported
story — adding a new story automatically adds a snapshot test.

63 snapshot tests across 18 component files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

5466d11 refactor(web): drop vite-tsconfig-paths in favor of resolve.alias

Click to expand commit body
Vite 8 no longer needs the plugin. Use resolve.alias to map @/* to
src/* directly, silencing the deprecation warning on every build/test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

269a31a feat(web): add @storybook/addon-a11y for accessibility testing

Click to expand commit body
Integrates axe-core via the a11y addon. Violations fail tests by
default (test: "error"). The addon adds an accessibility panel in
Storybook UI and a vision impairment simulator in the toolbar.

Also fix preview.ts import to use @storybook/react-vite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

c6f3106 fix(web): align vitest setup with storybook docs

Click to expand commit body
- Restore setupFiles in storybook project (addon detects it, skips
  auto-provisioning, but it's still needed per docs)
- Add storybookScript for watch mode DX (story links in failures)
- Switch snapshot tests from render() to run() which goes through
  the full Storybook lifecycle (loaders, decorators, play functions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

d4c83d9 feat(web): add @storybook/addon-vitest for browser smoke tests

Click to expand commit body
Set up the official Storybook Vitest addon which auto-transforms every
story into a Vitest test running in a real Chromium browser via
Playwright. Works alongside the existing snapshot tests.

Two vitest projects:
- storybook: browser-mode smoke/interaction tests (Playwright)
- snapshot: portable stories snapshot tests (happy-dom)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

6c3b7c8 feat(web): set up vitest with storybook for snapshot testing

Click to expand commit body
Configure vitest with happy-dom and the portable stories API to
run snapshot tests against Storybook stories. Adding a story
automatically adds a snapshot test.

Includes Button snapshot tests as the initial example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

7b39409 refactor(web): remove old QueryInput component

Click to expand commit body
Replaced by the provider-based ui/query-input composition component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

84e923c refactor(web): extract QueryInput as provider-based composition component

Click to expand commit body
New generic QueryInput with pluggable CompletionProviders:
- Root manages state (cursor tracking, keyboard nav, suggestions) via context
- Input renders the two-layer syntax-highlighted input
- Completions renders the autocomplete dropdown
- Icon is a positioned slot

Providers define prefix, highlight color, and getSuggestions (sync or async).
Adding a new filter type means adding a provider — zero component changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

377c1b7 refactor(web): remove deprecated BugRow component

Click to expand commit body
All consumers now use the IssueRow composition components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

484e74c refactor(web): migrate CommentBox to WritePreview (controlled)

Click to expand commit body
Replace inline write/preview tab rendering with WritePreview
composition component in controlled mode, since CommentBox resets
preview state after submitting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

11a54fb refactor(web): migrate new issue form to WritePreview

Click to expand commit body
Replace inline write/preview tab logic with WritePreview composition
component in uncontrolled mode. Removes manual preview state management.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

15629cf refactor(web): migrate user profile to IssueRow composition

Click to expand commit body
Replace 40-line inline bug row rendering with IssueRow compound
components. No hover, no label links, no author — expressed naturally
by what the consumer composes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

18e4ab8 refactor(web): migrate issues list to IssueRow + LabelBadgeLink

Click to expand commit body
Replace BugRow with IssueRow composition components in the issues list.
Labels now use LabelBadgeLink (createLink-wrapped) instead of onClick.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

eb6798b feat(web): add IssueRow, StatusTabs, and WritePreview composition components

Click to expand commit body
IssueRow: compound component (Root/StatusIcon/TitleArea/Meta/CommentCount)
replacing the flat prop-heavy BugRow.

StatusTabs: createLink-wrapped tab with OpenIndicator/ClosedIndicator/Count
sub-components for the open/closed toggle pattern.

WritePreview: context-based write/preview editor with controlled and
uncontrolled modes, replacing duplicated tab logic.

Also refactor LabelBadge: remove onClick prop, add LabelBadgeLink via
createLink for navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

336eb68 feat(web): add stories for all presentational components

Click to expand commit body
Add Storybook stories for:
- UI primitives: Badge, Input, Textarea, Separator, Skeleton, Avatar
- Bug components: StatusBadge, LabelBadge, BugRow
- Code components: RefSelector, FileTree, FileViewer, CodeBreadcrumb
- Content: Markdown

Also add a TanStack Router decorator for components using <Link>.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

9995c1f fix(web): remove unnecessary type assertion in _code route

Click to expand commit body
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

4a75d14 feat(web): add @tanstack/eslint-plugin-router via oxlint jsPlugins

Click to expand commit body
Enforce proper property ordering when creating routes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

30a03de feat(web): add eslint-plugin-storybook via oxlint jsPlugins

Click to expand commit body
Load the Storybook ESLint plugin through oxlint's jsPlugins support,
scoped to *.stories.tsx files via an override. Also fix the Button
story import to use @storybook/react-vite per no-renderer-packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created

219a715 feat(web): bootstrap Storybook 10 for component development

Click to expand commit body
Set up Storybook with @storybook/react-vite for isolated component
development and visual testing. Includes a Button story showcasing
all variants and sizes as a starting point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Quentin Gliech and Claude Opus 4.6 (1M context) created