{"session": "8761b1fd-7c24-4c0d-ada1-345c90852a5c", "env": "main-coco-runwright", "command": "journal helper done 3787 \"the dashboard holds each type's rows apart and sends report, dump and plan rows as summaries; the viewer reads a row whole when it opens one\" 2>&1|tail -1; journal helper report \"Alfred: to-do 3787, branch dashboard-pieces, tip e45e5e1cd, rebased on origin/main 264e1df21 (2.267.37); three commits (an earlier unfinished-work marker that is the first half, the work, and the rebuilt dist). Measured on the scratch copy of the live record (6367 messages, 2821 comments, 1045 to-dos), the whole dashboard in-process through the real route, each read preceded by a real new message written to the copy, since agent rows and messages change every few seconds: BEFORE (main, the whole body held as one): 33 to 54 ms per read (mean 43.6 ms), body 1.09 MB; AFTER: 23 to 40 ms (mean 28.8 ms), body 0.84 MB; with nothing changed both are 7 to 8 ms; the first read after a server start is about 1 s either way (the boot warm-up pays it before the viewer connects). So the held body was rebuilt on nearly every poll once a type changed, as you suspected; I could not instrument the live server itself, this is the same record's shape on the copy. What changed: surfaces/listing.py held_rows holds each type's JSON piece apart, keyed by that type's own rows and the settings, and dashboard() joins the pieces; a change to messages rebuilds only messages. Summaries: a resource declares summary_in_dashboard (new ClassVar), set for report, dump and plan; their dashboard rows keep title, abstract, state and data, the first 300 characters of the text and the titles of their sections (bodies empty), marked summary; the viewer reads a row whole when a page or panel opens it (Reader.vue calls whole() in sync/rows.js), and a poll keeps a whole row that has not changed (stillWhole in took). NOT summarized, deliberately: documents. The library's preview and its client-side search (by words inside a document, with where it matched) read the dashboard's full document rows, so summarizing them breaks both (the existing documents scenario 'a search finds a document by words inside it' failed until I took documents out); making that work needs a server-side search, which is its own to-do. That is why the body is 0.84 MB and not smaller: documents are 207 KB of it. Tests I ran, only these: tests/test_every_action.py's listing test (extended: a report's dashboard row is a summary with titles but empty bodies and the row asked by number is whole; a change to the to-dos keeps the reports' held piece) and the documents browser scenarios of tests/test_the_viewer.py (extended: a long report opened on its page shows its last words): 2 passed together before the rebase; the viewer is rebuilt after it. Under budget in this measure: every read; at load 50 the same work stretches, so the live number may still show 50 ms at busy moments. Next: 3783 (work await).\" 2>&1|tail -1", "parts": ["journal helper done 3787 \"the dashboard holds each type's rows apart and sends report, dump and plan rows as summaries; the viewer reads a row whole when it opens one\" 2>&1|tail -1", "journal helper report \"Alfred: to-do 3787, branch dashboard-pieces, tip e45e5e1cd, rebased on origin/main 264e1df21 (2.267.37); three commits (an earlier unfinished-work marker that is the first half, the work, and the rebuilt dist). Measured on the scratch copy of the live record (6367 messages, 2821 comments, 1045 to-dos), the whole dashboard in-process through the real route, each read preceded by a real new message written to the copy, since agent rows and messages change every few seconds: BEFORE (main, the whole body held as one): 33 to 54 ms per read (mean 43.6 ms), body 1.09 MB; AFTER: 23 to 40 ms (mean 28.8 ms), body 0.84 MB; with nothing changed both are 7 to 8 ms; the first read after a server start is about 1 s either way (the boot warm-up pays it before the viewer connects). So the held body was rebuilt on nearly every poll once a type changed, as you suspected; I could not instrument the live server itself, this is the same record's shape on the copy. What changed: surfaces/listing.py held_rows holds each type's JSON piece apart, keyed by that type's own rows and the settings, and dashboard() joins the pieces; a change to messages rebuilds only messages. Summaries: a resource declares summary_in_dashboard (new ClassVar), set for report, dump and plan; their dashboard rows keep title, abstract, state and data, the first 300 characters of the text and the titles of their sections (bodies empty), marked summary; the viewer reads a row whole when a page or panel opens it (Reader.vue calls whole() in sync/rows.js), and a poll keeps a whole row that has not changed (stillWhole in took). NOT summarized, deliberately: documents. The library's preview and its client-side search (by words inside a document, with where it matched) read the dashboard's full document rows, so summarizing them breaks both (the existing documents scenario 'a search finds a document by words inside it' failed until I took documents out); making that work needs a server-side search, which is its own to-do. That is why the body is 0.84 MB and not smaller: documents are 207 KB of it. Tests I ran, only these: tests/test_every_action.py's listing test (extended: a report's dashboard row is a summary with titles but empty bodies and the row asked by number is whole; a change to the to-dos keeps the reports' held piece) and the documents browser scenarios of tests/test_the_viewer.py (extended: a long report opened on its page shows its last words): 2 passed together before the rebase; the viewer is rebuilt after it. Under budget in this measure: every read; at load 50 the same work stretches, so the live number may still show 50 ms at busy moments. Next: 3783 (work await).\" 2>&1|tail -1"]}