Files
PaperClipAI/doc/plans/2026-10-26-list-cold-open.md
T
DottaandPaperclip 5061ab7b35 perf(ui): speed up task and inbox cold opens (#17756)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task lists and inboxes are the main entry points to that work.
> - Cold opens fetch overlapping task lists, task descriptions, and run
details before useful rows settle.
> - Independent responses can insert rows above content that a person
has started to read.
> - This pull request loads a small task index first and adds details
only for visible rows.
> - It keeps row order and geometry stable while those details arrive.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Cold opens of Tasks and Inbox, including the shared navigation badge.

**Subsystem affected**

Server list reads, shared response contracts, and the React UI.

**Current behavior**

The badge fetches full task bodies. Mine fetches several overlapping
lists. Historical diagnostics and row metadata add work before the first
useful list. A fast failed-run response can appear before task rows,
then move down when tasks arrive.

**Proposed behavior**

Load the selected inbox source and its required context together. Use an
index without task descriptions or recovery diagnostics. Fetch
diagnostic details for up to 50 visible rows. Keep skeletons, recovery
indicators, external status, and live metadata in fixed space.

**Reason and benefit**

Reduce work on a cold open and prevent loading-induced row movement. On
the same 100 local fixture tasks, the compact response was 262,441
decoded bytes and the index was 110,909 bytes. This comparison excludes
compression and is not a page latency benchmark.

**Breaking changes**

Existing full and compact responses remain available. New index/row
projections are opt-in and marked as partial data. Mine keeps its
existing most-recent-100 display limit, now selected on the server.
Existing filters and group preferences remain in the client. No database
migration.

**Additional context**

Refs: #17593. Related: #13095, #17428, #13803. This change addresses
collection overfetch and layout stability. It does not replace the
blocked-inbox query work or the database activity-index work.

## What Changed

- Add authorized index and row projections, with bounded task and parent
ID batches.
- Replace badge list downloads with unread IDs and aggregate counts.
Preserve actor privacy, dismissals, and local archive behavior.
- Load only the selected inbox source, plus bounded parent and
failed-run context. Return only relevant failures from the bounded run
window.
- Batch hierarchy reads, including at most one authorized child per
collapsed parent in one request. Collapsed markers do not consume the
expanded row budget, and partial descendant counts are not shown as
totals. Enrich visible rows without replacing membership, ordering,
titles, or read state.
- Reserve row geometry on desktop and mobile. Reveal the initial inbox
only after its sources settle. Preserve legacy task-list reads.
- Start collection module downloads during access checks. Keep partial
rows out of the task-detail cache.
- Keep ETags and concurrent request sharing for the new projections, but
skip the short server response TTL so archive undo sees fresh state.

## Verification

- On final head `ef6afb2a56`, the full CI workflow passed, including
build, typecheck, server/workspace/runner suites, and all eight browser
shards. A server shard reported **1,489 passing tests** before runner
shutdown canceled the job; its single infrastructure retry passed. Both
Greptile and Paperclip Review are **5/5**, with zero unresolved review
threads.
- The optional external **Entire Gates** check remains `unassessed`
because it has no active assessed Change for this comparison. Entire is
not set up in this checkout. The repository ruleset requires `ci /
verify` and `ci / e2e`; both passed. No gate configuration was changed.

- Passed workspace typecheck after the final API batching change. The
full production build also passed on the final commit; the exact-retry
E2E run built the shipped UI.
- Passed token gates and map validation. The final batch-preview pass
has 91 passing route/hierarchy/component tests, including privacy,
request bounds, 30 collapsed parents in one read, and a 120-child parent
alongside a sibling.
- Passed the 166-test focused UI/inbox/badge run, 47 additional UI
regression tests, and 34 issue-list route tests. The latest regressions
cover low-trust agent badge IDs, cold company selection, 30
description-only Mine matches with archive actions, retrying optional
row details, and collapsed/expanded siblings.
- The full local `pnpm test:run` spanned the rebase and reported
failures in five suites before it was stopped; it is not claimed as a
complete pass. A fresh serial rerun of all five suites passed
**452/452** tests (tool gateway, native resume, project repositories,
workspace recovery, and runner selection). Full current-commit coverage
is checked by CI.
- Real browser: delayed index and row-detail responses on Tasks and
Mine. The first placeholder and first row had the same origin and
44-pixel height. Twelve rows on each page had zero position, height, or
title-width change after recovery details arrived.
- Real browser: reviewed long titles and recovery status at a 390 by 844
viewport. Task text stays on one line and recovery uses a compact
indicator.
- Three fresh document loads per page on final commit `260893bf92`, with
HTTP cache retained on a warm local development server: Tasks
1.031–1.190 seconds; Mine 0.999–1.193 seconds. Browser-control overhead
is included. These values must not be compared directly with staging
latency or the earlier local samples taken during a full test run.
- The browser archive test exposed stale results after Undo. The new
projections now bypass the server TTL; immediate-mutation regression
tests pass. The final browser retry passed: Archive followed by Undo
restored exactly one row and the badge count.

- CI exposed a hook-order crash when company selection resolved after
the first inbox render. The hook now runs before the empty-company
return. All seven affected exact-retry and legacy-continuation browser
scenarios pass locally after the fix.
- Real browser review regressions: 97 description-only source matches
retained Archive actions; deliberately failed optional row reads
displayed a retry notice while all 12 observed row positions and heights
stayed unchanged. Retry restored the recovery indicator. Network fault
injection was removed.

- The final production build and workspace typecheck passed. Final
focused verification passed 153 UI/helper tests and 26 heartbeat privacy
route tests. Final review regressions cover newer diagnostics arriving
during the index read, reloading diagnostics when the same task ID
changes version, optional source failure/retry without hiding available
rows, and required external-status filter failure. The latest-failures
route test verifies bounded selection, agent filtering, success
suppressing an older failure, and private-run redaction.
- Real browser: deliberately failed the run source, then retried it.
Twelve available task rows kept the same position and 44-pixel height
before, during, and after retry. No loading skeleton replaced them.
Fault injection was removed.
- Hierarchy review decision: the cold-open barrier stays because
inserting ancestors or expanded children after revealing index rows
would cause layout shifts. Existing hierarchy context now survives
subsequent requests, with a regression proving a new ancestor read does
not blank the list. The bounded recursive tree read can still add
latency for unusually deep expanded trees.

- Tasks now handles a failed external-status filter with an explicit
error and Retry, suppressing a false empty state. All 52 Tasks component
tests pass, including failure-to-success retry. One existing large-tree
test timed out while workspace typechecking ran concurrently; the whole
component suite passed when rerun without that competing work. Workspace
typecheck, the full production build, and token gates passed for this
fix.

## Risks

- Saved filters and grouping still operate on the existing bounded
collection. This is not a new global server pagination model.
- Fixed row slots reduce available title width. Long titles remain
accessible through task navigation.
- Recovery and related diagnostics arrive after core rows. Their
responses cannot replace ordering fields, and stale versions are
rejected.
- Index/row requests now revalidate against current data instead of a
short server TTL. They still share concurrent reads and use ETags.
- This has not been deployed by this pull request. Staging latency must
be measured after deployment under comparable conditions.

## Model Used

OpenAI Codex, GPT-6 family. The exact runtime model ID and
context-window size are not exposed in this session. Used code editing,
terminal execution, and real-browser diagnostics.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-26 14:29:20 -05:00

6.4 KiB
Raw Blame History

Task list and inbox cold opens

Outcome and ownership

Cold-open /issues and /inbox/* should show useful, correctly ordered rows without inserting earlier rows or changing row geometry as supporting data arrives. This work owns the collection read contracts, their UI consumers, and navigation badge reads. Keep issue authorization, mutation semantics, archive/undo, and existing grouping/filter preferences with their current owners. The separate staging performance effort (PR #17745) owns runner event-loop stalls.

Branch: codex/list-cold-open, rebased onto 063a498b54. Endpoint: tested, reviewable PR with checks and review resolved; local test drive. No staging infrastructure changes are needed for this implementation.

Evidence and acceptance

Baseline on staging, authenticated browser with HTTP cache retained and fresh application state: task rows ~4.5s; Mine task rows ~6.3s. Mine downloaded four overlapping issue responses (500, 167, 52, 52 rows; 2,388,103 decoded bytes). A failed-run row appeared at y=183 before tasks arrived, then moved to y=415. These are individual observations, not percentile benchmarks.

Journey Required result Evidence
Cold Tasks / Mine Bounded initial data; final row order before reveal; metadata fills stable slots Request sizes, delayed-response browser trace, row geometry
Other inbox tabs and saved filters/grouping/nesting Correct membership/order before paging, no false empty state Focused contract/component tests plus local browser
More results and detail/back No lost or duplicated rows; reading position preserved Browser walkthrough and paging regressions
Archive/undo/read and background activity Existing optimistic behavior survives; observed rows stay put Existing regression suites and browser
Slow/failed metadata or list read Stable layout; useful retry/error; no misleading partial inbox Deferred request component tests and browser
Company/account boundaries No cross-company or private-row leaks in lists or badges Authorization tests

Implementation sequence

  1. Remove full-list badge reads; preserve personal unread/archive/dismissal semantics.
  2. Add a lean ordering index using existing pagination/filter contracts, then enrich only visible rows (at most 50). Batch hierarchy reads rather than fetching full task bodies per parent. Mine selects its most recent 100 rows server-side; existing client grouping/filtering remains in place. This is not a new global server-side pagination model for every saved view.
  3. Commit inbox source data together rather than revealing unrelated responses in arrival order.
  4. Match skeleton and row geometry. Enrichment cannot replace index membership, sorting fields, or row geometry; retain the existing inbox order pin.
  5. Preload the requested collection module during access/company checks and stop downloading task detail code eagerly on collection opens.
  6. Verify in the existing isolated local app, run required checks, prepare PR and resolve review.

Use targeted tests while iterating; full repository verification before PR-ready handoff. Do not infer deployed speedup from local measurements or combine the separate server fix's savings.

State

Implemented the bounded index, visible-row enrichment, compact badge reads, and initial snapshot barrier. Legacy task lists retain their existing compact data. Index and row requests retain ETags and in-flight coalescing but skip the short server response TTL, so an archive undo immediately reads the changed state.

Local browser proof uses 160 seeded tasks, nested rows, long titles, unread activity, and a saved recovery action. On desktop, the first Tasks placeholder and task both start at y=163 with height 44. The equivalent inbox values are y=175 and height 44. Delayed row enrichment produced zero position, height, or title-width change across 12 measured rows on each page. Narrow-screen review at 390 by 844 confirmed truncation and a compact recovery indicator.

Three fresh document loads with retained HTTP cache and a warm local Vite server showed Tasks in 2.054–2.170s and Mine in 2.115–2.268s after the archive/undo fix. These include browser-control overhead while the test suite ran on the same computer. They are not comparable to staging latency. For the same 100 local tasks, the old compact response was 262,441 decoded bytes and the index was 110,909 bytes, with no descriptions.

Workspace typecheck, production build, token gates, map validation, and focused UI/server regressions passed. The initial full local run spanned a rebase and was stopped after reporting failures; a clean serial rerun of all five affected suites passed 452 tests. Fresh current-commit CI is the full-suite acceptance gate.

Review fixes preserve description-only inbox matches through a scoped, on-demand index search and keep unread task IDs board-only. Collapsed parents use a batched one-child preview instead of spending the expanded row budget or making a request per parent. Optional-detail failures show a retry notice outside document flow; 12 measured rows remained unchanged and retry restored the recovery indicator. All seven exact-run retry/legacy continuation browser scenarios passed after fixing the company-loading hook order. Archive/Undo, read/back, nesting, remote search and rendering all 164 fixture tasks without duplicates also passed. PR: https://github.com/paperclipai/paperclip/pull/17756. Browser evidence is retained in the chat's list-cold-open-test-drive artifact directory. Existing task-detail PR #17593 is already merged and verified on staging; its paused hourly monitor is not part of this change.

The final review pass accepts newer diagnostic snapshots while retaining index ordering, and refreshes visible diagnostics when a task version changes. Optional inbox source failures keep available rows and expose retry outside document flow; required external-status filter failures show an error rather than an endless skeleton. A browser-injected failed run source retained 12 rows at the same positions and heights before, during, and after retry. The initial hierarchy barrier remains intentional to avoid inserting ancestors or children above rows already being read; subsequent hierarchy requests retain the previous context. Server route coverage now verifies latest-failure filtering, bounded windows, agent filters, and private-run redaction. The unused client badge implementation and its obsolete tests were removed; the production server badge tests remain.