Bristlenose Design System

Two-layer architecture: atomic CSS design tokens (shared by all render paths) + React components (the active development surface in serve mode). CSS custom properties are the single source of truth for visual decisions. Dark mode uses light-dark() — no JS required.

Architecture

Two render paths, one design system

How the layers connect

Bristlenose has two rendering paths that share the same CSS tokens and stylesheets. React serve mode (bristlenose serve) is the actively developed experience. Static HTML (bristlenose render) is the frozen offline snapshot — correct data, no new design work.

CSS (shared, both paths)

  • tokens.css — design tokens
  • atoms/ — 18 files
  • molecules/ — 16 files
  • organisms/ — 9 files
  • templates/ — 3 CSS + 14 HTML

React (serve mode only)

  • 20 component primitives
  • 12 islands (mounted via comment markers)
  • 3 custom hooks
  • 8 utility modules
  • Module-level store (not Context)

Island mount pattern

Static HTML contains comment markers (<!-- bn-toolbar -->). At serve time, re.sub in app.py replaces them with React mount divs (<div id="bn-toolbar-root">). main.tsx calls createRoot() on each mount point.


Layer 0

Design Tokens

The single source of truth for all visual decisions. tokens.css defines colours, spacing, radii, typography, transitions, and dark mode overrides via light-dark(). Override these CSS custom properties to theme the entire application.

Colour palette

--bn-colour-bg#ffffff
--bn-colour-text#1a1a1a
--bn-colour-muted#6b7280
--bn-colour-border#e5e7eb
--bn-colour-accent#2563eb
--bn-colour-hover#e8f0fe
--bn-colour-quote-bg#f9fafb
--bn-colour-badge-bg#f3f4f6
--bn-colour-editing-bg#fffbe6
--bn-crop-handle-colour#c4850a
--bn-colour-negative#dc2626

Font weights (variable font scale)

420 normal 490 emphasis 520 starred 700 strong

Sentiment colours (v0.7+ taxonomy)

frustration#ea580c / #fff7ed
confusion#dc2626 / #fef2f2
doubt#7c3aed / #f5f3ff
surprise#d97706 / #fffbeb
satisfaction#16a34a / #f0fdf4
delight#059669 / #ecfdf5
confidence#2563eb / #eff6ff

Codebook tag colours (pentadic OKLCH)

UX setH 225–275 (blue)
Emotion setH 340–40 (red-pink)
Task setH 130–180 (green)
Trust setH 275–325 (purple)
Opportunity setH 50–100 (amber)

Spacing scale

Border radii

sm 3px
md 6px
lg 8px
pill

Typography

Body: Inter Variable, Segoe UI Variable, system-ui
Mono: SF Mono, Fira Code, Consolas

File: tokens.css. Dark mode via light-dark() + color-scheme: light dark on :root. User override via [data-theme="light|dark"] attribute. ~87% browser support (mid-2024+).


Layer 1

Atoms

Smallest indivisible building blocks. Each atom is a single CSS file in bristlenose/theme/atoms/. Atoms reference only design tokens, never other atoms. 18 files.

narration frustration delight confidence
Badge (sentiment)
atoms/badge.css
.badge .badge-{sentiment}
React: <Badge>
usability emotion proposed + add tag
Badge (user + proposed + add)
atoms/badge.css
.badge-user .badge-proposed .badge-add
Toolbar button
atoms/button.css
.toolbar-btn
React: <ToolbarButton>
star, pencil, hide
Icon buttons
atoms/toggle.css
.star-btn .edit-pencil .hide-btn
[02:34]    [14:07]
Timecode
atoms/timecode.css
.timecode .timecode-bracket
React: <TimecodeLink>
p1 Sarah Chen
 
m1 Moderator
Split speaker badge
molecules/person-badge.css
.bn-speaker-badge--split -code -name
React: <PersonBadge>
video keyframe / placeholder
Thumbnail
atoms/thumbnail.css
.thumbnail .thumbnail-img .thumbnail-placeholder
React: <Thumbnail>
Copied to clipboard
Toast notification
atoms/toast.css
.bn-toast
React: <Toast> + toast()
session sentiment
Sparkline
molecules/sparkline.css
React: <Sparkline>
● Running ✓ Done
Activity chip
atoms/activity-chip.css
.activity-chip -pending -complete -error
React: <ActivityChip>
frustration
12
delight
7
Histogram bar
atoms/bar.css
.sentiment-bar .sentiment-bar-label .sentiment-bar-count
Modal + buttons
atoms/modal.css
.bn-overlay .bn-modal .bn-btn

Also: atoms/input.css (text input base), atoms/logo.css, atoms/footer.css, atoms/interactive.css, atoms/span-bar.css (vertical extent bars), atoms/context-expansion.css, atoms/journey-label.css, atoms/moderator-question.css, atoms/autocode-toast.css.


Layer 2

Molecules

Groups of atoms forming single functional units. Files live in bristlenose/theme/molecules/. 16 files.

frustration usability emotion + add
Badge row
molecules/badge-row.css
.badges
Search filter
molecules/search.css
.search-container .search-input
React: <SearchBox> (150ms debounce, min 3 chars)
usability 12
navigation 8
onboarding 5
Tag filter dropdown
molecules/tag-filter.css
.tag-filter-menu .tag-filter-group .tag-filter-item
React: <TagFilterDropdown> (codebook groups, counts)
3 hidden click to preview + unhide
Hidden quotes badge
molecules/hidden-quotes.css
.bn-hidden .bn-hidden-badge .bn-hidden-dropdown
React: <Counter>
[ I couldn't find the settings ]
Editable text (crop mode)
molecules/editable-text.css
.crop-handle .crop-editable .crop-word .undo-btn
React: <EditableText> + useCropEdit()
Navigation Issues
frustration usability
Transcript annotation
molecules/transcript-annotations.css
.segment-margin .margin-label .margin-tags

Also: molecules/bar-group.css, molecules/quote-actions.css, molecules/tag-input.css, molecules/name-edit.css, molecules/help-overlay.css, molecules/feedback.css, molecules/autocode-report.css, molecules/threshold-review.css, molecules/sparkline.css, molecules/person-badge.css.


Layer 3

Organisms

Complex components composed of molecules and atoms, forming distinct sections of the interface. Files live in bristlenose/theme/organisms/. 9 files.

[02:34]
I kept clicking on the logo expecting it to take me home but nothing happened
p1 Sarah Chen
frustration navigation + add
Quote card
organisms/blockquote.css
.blockquote .speaker .edit-pencil .badges
React: <QuoteCard> (star, hide, edit, crop, tags, badges)
Toolbar (sticky, Quotes tab)
organisms/toolbar.css
.toolbar #bn-toolbar-root (serve mode sticky)
React: <Toolbar> island (SearchBox + TagFilterDropdown + ViewSwitcher + CSV)
Global navigation
organisms/global-nav.css
.bn-global-nav .bn-tab .bn-tab-panel .bn-tab-spacer .bn-tab-icon
UX Issues
navigation usability discoverability
Codebook group card
organisms/codebook-panel.css
.codebook-grid .codebook-group .tag-row
React: <CodebookPanel> island (drag-drop, inline edit)
frustration
12 confusion
7 doubt
4
Analysis (signal cards + OKLCH heatmap)
organisms/analysis.css
.signal-cards .signal-card .heatmap .heatmap-cell
React: <AnalysisPage> island

Also: organisms/sentiment-chart.css, organisms/coverage.css, organisms/toc.css, organisms/settings.css.


Serve mode

React Islands

Islands are React components mounted into static HTML via comment markers. Each gets its own createRoot() call. Cross-island state shares via a module-level store (useSyncExternalStore), not React Context.

IslandMount markerPurpose
Dashboard <!-- bn-dashboard --> Project tab: stat cards, featured quotes, session summary
SessionsTable <!-- bn-session-table --> Sessions tab: interview table with thumbnails, sparklines, drill-down
Toolbar <!-- bn-toolbar --> Quotes tab: sticky search + tag filter + view switcher + CSV export
QuoteSections <!-- bn-quote-sections --> Quotes tab: screen-specific insights with quote cards
QuoteThemes <!-- bn-quote-themes --> Quotes tab: cross-session thematic patterns with quote cards
CodebookPanel <!-- bn-codebook --> Codebook tab: masonry tag groups, drag-drop, inline editing
AnalysisPage <!-- bn-analysis --> Analysis tab: signal cards, OKLCH heatmap, drill-down
TranscriptPage <!-- bn-transcript-page --> Standalone transcript pages: segments, timecodes, annotations
SettingsPanel <!-- bn-settings --> Settings tab: appearance (auto/light/dark), speaker badge display
AboutPanel <!-- bn-about --> About tab: version, documentation links, design mockups

Layer 4

Templates & Pages

Page-level layout that places organisms into a structure. Templates define the skeleton — where things go, not what they contain.

FilePageKey layout decisions
templates/report.css Main report Max-width (52rem), article centering, header + metadata, section spacing, blockquote grid, tabbed panels
templates/transcript.css Per-session transcript Segment layout with speaker codes, right-margin annotations (≥ 1100px), anchor-highlight animation, moderator de-emphasis
templates/print.css Print / PDF export Forces light colour-scheme, hides toolbar/footer/navigation, adjusts page margins

Generated pages

PageFileRender pathDescription
Report bristlenose-{slug}-report.html Static + Serve (10 React islands) Main research report: 7 tabs, extracted quotes, themes, sentiment, search, editing
Transcript sessions/transcript_s{n}.html Static + Serve (1 React island) Per-session transcript with timecoded segments, speaker codes, annotations

Reference

Complete file map

LayerFilePrimary components
Tokenstokens.cssAll CSS custom properties (colours, spacing, radii, transitions, dark mode via light-dark())
Atomsatoms/activity-chip.cssBackground job status chips (pending, complete, error)
atoms/autocode-toast.cssAutoCode progress notification
atoms/badge.cssBadge base, 7 sentiment variants, user/AI tags, proposed + accept/deny pill, animations
atoms/bar.cssHistogram bars, counts, labels
atoms/button.cssToolbar button (.toolbar-btn)
atoms/context-expansion.cssExpandable context sections
atoms/footer.cssFooter layout, links, logo
atoms/input.cssText input base styles
atoms/interactive.cssGeneric interactive element states
atoms/journey-label.cssUser journey step labels
atoms/logo.cssLogo image + logotype
atoms/modal.cssOverlay, modal card, action buttons
atoms/moderator-question.cssModerator context pill
atoms/span-bar.cssVertical extent indicator bars
atoms/thumbnail.cssVideo session thumbnail with play icon
atoms/timecode.cssTimecode links, brackets
atoms/toast.cssToast notification
atoms/toggle.cssStar, hide, toolbar toggle buttons
Moleculesmolecules/autocode-report.cssAutoCode proposal triage table
molecules/badge-row.cssFlex row of badges
molecules/bar-group.cssHistogram bar group (display: contents)
molecules/editable-text.cssInline edit + crop bracket handles + undo
molecules/feedback.cssFeedback form
molecules/help-overlay.cssKeyboard shortcut help
molecules/hidden-quotes.cssHidden quote badge + dropdown preview
molecules/name-edit.cssParticipant name cells with pencil-on-hover
molecules/person-badge.cssSplit speaker badge (two-tone code+name pill)
molecules/quote-actions.cssBulk action hover preview states
molecules/search.cssExpanding search field
molecules/sparkline.cssMicro sentiment bar chart
molecules/tag-filter.cssTag filter dropdown with codebook groups
molecules/tag-input.cssTag input with autocomplete
molecules/threshold-review.cssConfidence threshold slider + zones
molecules/transcript-annotations.cssRight-margin annotations with span bars
Organismsorganisms/analysis.cssSignal cards, OKLCH heatmap grid, cell tooltips
organisms/blockquote.cssFull quote card with all actions
organisms/codebook-panel.cssCodebook group grid, drag-drop, inline edit
organisms/coverage.cssTranscript coverage details
organisms/global-nav.cssTab navigation + dashboard + featured quotes + session drill-down
organisms/sentiment-chart.cssSide-by-side histogram layout
organisms/settings.cssSettings panel layout
organisms/toc.cssTable of contents
organisms/toolbar.cssSticky toolbar, view switcher, scroll-margin offsets
Templatestemplates/report.cssReport page layout
templates/transcript.cssTranscript page layout
templates/print.cssPrint overrides

React components (frontend/src/)

LayerFilePurpose
Hooks & utilshooks/useCropEdit.tsQuote text crop editing (trim start/end with bracket handles)
hooks/useDropdown.tsShared dropdown dismiss (Escape + click-outside)
hooks/useTranscriptCache.tsSession transcript segment caching
utils/filter.tsfilterQuotes() — search + view mode + tag filter
utils/highlight.tsxhighlightText() — search match <mark> wrapping
utils/colours.tsCodebook colour mapping (mirrors vanilla JS)
utils/toast.tsImperative toast() function (detached React root)
utils/sequences.tsConsecutive quote detection for signal cards
Primitivescomponents/Badge.tsxSentiment, user tag, proposed (accept/deny) badges
components/PersonBadge.tsxSplit speaker badge (code + name, display toggle)
components/SearchBox.tsxToolbar search input (150ms debounce, min 3 chars)
components/TagFilterDropdown.tsxCodebook-grouped tag filter with counts
components/ViewSwitcher.tsxAll / Starred view mode dropdown
components/ToolbarButton.tsxStyled button atom (icon + label + arrow)
components/Toast.tsxNotification component
Storecontexts/QuotesContext.tsxModule-level store: quotes, filters, mutations (useSyncExternalStore)