Dosu LogoDosu Logo
Ask
Join our Discord
Organization avatar
Dosu DecantPublic
Dosu
DocumentsDosu Decant
Session List UI Constraints
Session List UI Constraints
Type
Topic
Status
Published
Created
Aug 11, 2026
Updated
Aug 11, 2026

Session List UI Constraints#

Three non-obvious constraints in src/ui/main.tsx affect testing, sidebar stats, and pagination. Misunderstanding any one of them causes subtle regressions.


1. createRoot fires at import time — UI tests read source text instead#

main.tsx calls createRoot(root).render(...) at module scope, outside any function. Importing the file in a test environment immediately tries to access document.getElementById("root"), which fails or mounts a live React tree.

Consequence: Every UI test for main.tsx reads the file as a raw string with readFileSync and asserts on source substrings:

  • test/ui-loading-state.test.ts
  • test/ui-sync-copy.test.ts
  • test/ui-interaction-contracts.test.ts

(Additional files test/ui-restyle-contract.test.ts, test/ui-recovery-report.test.ts, and test/ui-dosu-copy.test.ts follow the same pattern.)

Corollary: A source-text assertion fails when a line is reworded, even if the behavior is unchanged. Treat a red source-text assertion as "restate the contract," not "you broke something."

Path forward: If you need a real render test, extract the logic into a standalone module first. src/ui/loading-state.ts and src/ui/navigation.ts exist for exactly this reason. test/ui-transcript-markdown.test.tsx demonstrates the pattern — it renders transcript-markdown.tsx with renderToStaticMarkup because that file has no module-level side effects.


2. Sidebar "latest date" comes from loaded rows, not a dedicated endpoint#

latestSessionDay() finds the first session with a non-null started_at using Array.find(). Because the session list is loaded newest-first, that first row is the most-recent session — but only when the loaded slice starts at offset: 0.

The sidebar usage guards for this explicitly :

loadedPage === 1 ? latestSessionDay(sessions) : null
    ?? formatDay(data.dateBounds?.max ?? null)

When on any page other than page 1, it falls back to data.dateBounds?.max from /api/date-bounds, which returns the archive-wide max date regardless of any active filter.

Risk area: Any change that reorders the session list, introduces client-side filtering before passing rows to latestSessionDay, or changes when loadedPage is 1 will silently shift the sidebar date. The /api/date-bounds fallback is not scoped to the active project filter.


3. pageSize + 1 rows are requested as a pagination probe#

planSessionPageLoad always requests pageSize + 1 rows. The extra row is a probe: if the API returns a full pageSize + 1 result, a next page exists. If it returns fewer, the list is exhausted. sessionPageExhausted encodes this check as receivedRows < requestedRows.

Why not use a total count? data.summary and the project-scoped summary are separate fetches. They are null during cold loads and while a project filter's scoped summary is in flight. A pagination control gated on a total would disappear or disable itself for the first moments of every such view.

Why the probe stays within server limits: SESSION_LIST_MAX_LIMIT is 100. At the default pageSize of 50, requesting 50 + 1 = 51 rows is well under that cap. The Math.min(SESSION_LIST_MAX_LIMIT, normalizedPageSize + 1) in planSessionPageLoad prevents the probe from exceeding the server's hard limit regardless of what pageSize is passed in.

Do not replace the probe with a total-derived control. The API's GET /api/sessions returns a plain array with no envelope — there is no server-side total to rely on.

Documents
Antigravity Model Canonicalization and Cost Fallback
Antigravity Plan Mode Ingest Gap
Antigravity Two-File Source Identity
Antigravity Usage and Cost Capture
Badge Backdrop-Filter Checkerboarding
Bun Standalone Compilation
distribution
Claude API Cost Tracking
pricing
Codex MCP Call Rendering
Database Info Query Performance
Decant Runtime Architecture
AGENTS
Archive schema: incident history behind the migration rules
CONTRIBUTING
README
README
routes
Deleted Session Tombstone Semantics
SECURITY
Documentation Architecture
AGENTS
Decision log and roadmap context (through 2026-08-03)
History: the Rust/Elixir/Swift to TypeScript cutover (2026-07)
Gemini and Antigravity Source Replay Semantics
Gemini CLI Chat Record Parsing
Ingest-Time Cost Semantics
AGENTS
Archive schema: incident history behind the migration rules
Decision log and roadmap context (through 2026-08-03)
History: the Rust/Elixir/Swift to TypeScript cutover (2026-07)
pricing
README
README
Local API and Report Routes
AGENTS
CONTRIBUTING
README
README
routes
MCP Server Integration
MCP Server Slug Disambiguation
Recommendation Scoring
Release Automation
CONTRIBUTING
distribution
Session Analytics and Statistics
AGENTS
README
routes
Session Lifecycle Management
README
README
Session List UI Constraints
README
Sidechain Subagent Session Modeling
UI Restyle Contract Tests
CODE_OF_CONDUCT
PULL_REQUEST_TEMPLATE
README
README
README
README
README
Source-Serif-OFL
logging