feat(meetings): add a per-meeting note the user owns - #7194
Conversation
bolichen97
left a comment
There was a problem hiding this comment.
Description / code mismatch
Two surfaces the Description presents as complete land differently in the code: the note sidebar it describes as a finished editor ships without a focus cue, and the 100k-character ceiling it advertises is unreachable in the non-Latin scripts the dashboard is translated into.
1. The note textarea suppresses the global focus outline with no replacement cue
The Description says —
Frontend: a
NoteSidebar(edit/preview, autosave state, image paste with the meeting's elapsed time), opened from the meeting's overflow menu.
The code does — the textarea that is the editor carries focus:outline-none with no focus-ring, no focus-visible: cue, and no focus-within: cue on the enclosing <aside>, so the element the whole feature exists to type into has no visible focus indicator at all. website/src/apps/meetings/components/NoteSidebar.tsx:211.
Risk — a keyboard-only or low-vision user tabbing into the note editor cannot tell where focus is, which is WCAG 2.4.7 Level AA. The module contract at the top of scripts/check_focus_cue.py states that rule and states that focus:outline-none is a suppressor rather than a cue; the scanner therefore flags this line. That gate is a required check — focus-cue-lint / "Focus Cue Gate", listed in docs/ci/ci-and-reviews.md:126 and wired at .github/workflows/ci.yml:246 — and it is red on the head SHA, so the PR cannot merge in this state.
Required change — append focus-ring to the textarea's className, matching the precedent at website/src/components/AgentPanel.tsx:293, or add a focus-visible: cue on the textarea or a focus-within: cue on the enclosing <aside>. Then re-run FOCUS_CUE_BASE_REF=origin/main python3 scripts/check_focus_cue.py.
2. The note PUT keeps the shared 256 KiB body cap, so the advertised 100k-character ceiling is unreachable in non-Latin scripts
The Description says —
Markdown, autosaved, capped at
MAX_NOTE_CHARS(100k — a human typing for at most the 4 hoursMAX_SESSION_DURATIONallows).
The code does — handle_put_note calls json_body without a max_bytes argument, so the note inherits the shared _common.MAX_BODY_BYTES of 256 KiB, while the sibling whole-document route passes a route-specific MAX_MINUTES_BODY_BYTES sized for its own character cap. src/kiro_crew/apps/builtins/meetings/backend/routes/meeting_lifecycle.py:734. The character cap is enforced, the byte cap is not raised to cover it, and the two disagree for any text above one byte per character.
Risk — the dashboard ships in 12 languages. A Japanese, Chinese, Korean, Hindi, Bengali, or Russian note crosses 256 KiB of UTF-8 JSON at roughly 87,400 characters (about 65,500 for emoji, and about 43,000 for a client that \u-escapes non-ASCII), well under the advertised 100,000. From that point every autosave answers 413 and the frontend surfaces only the generic noteSaveFailed toast ("Could not save your note."), so everything the user types afterwards is silently unsaved — on the one file the code itself calls the thing in this app the user cannot regenerate. No test observes it, because test_put_accepts_a_note_at_the_cap uses "x" * MAX_NOTE_CHARS, which is ASCII.
This is not new arithmetic: test/test_meetings_minutes.py:516, class TestBodyCaps, already pins precisely this invariant on main, its functional case at :538 uses content = "議" * 100_000 — exactly MAX_NOTE_CHARS many CJK characters — asserting len(payload.encode()) > _common.MAX_BODY_BYTES, and its docstring already names the failure mode as the user being able to open the document, edit it, and only then be told it cannot be saved.
Required change — either give the note a route-specific body cap (a MAX_NOTE_BODY_BYTES sized as MAX_MINUTES_BODY_BYTES is, covering 100_000 * 12 plus envelope) and pass it to json_body in handle_put_note, or lower MAX_NOTE_CHARS to a value the shared 256 KiB cap actually covers and say so in the Description. Either way, extend the spec sentence at docs/system-specs/modules/meetings.md:461 on main — the same sentence sits at roughly line 478 in this head, unchanged by the diff — to name the second whole-document route, and mirror TestBodyCaps for the note with a multi-byte case.
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: This PR has been inactive for 7+ days with failing CI. I've assessed the blockers and they appear resolvable — I'll push fixes directly to this branch as a co-author. Assessment: Both blocking review findings come with exact file:line fixes — add a If you'd prefer I don't touch this PR, add the |
The minutes an agent writes belong to the agent. This adds a note that belongs to the user: free-form Markdown stored per meeting, saved explicitly, and kept separate from anything an agent generates. Images pasted into the note are stored as meeting-scoped attachments and referenced from the Markdown, so a screenshot taken during a call lands in the note instead of being lost with the clipboard.
b3f1950 to
d49f760
Compare
|
Rebased onto main Conflicts, resolved additively (no behaviour change to this PR):
Gates run locally on the rebased head: One pre-existing failure, unchanged by the rebase: Please review the resolution. A maintainer push makes the maintainer the last pusher, so a second approver is needed under the repo's last-push rule. Reply here if anything looks wrong. |
Problem / Motivation
Everything written during a meeting belongs to an agent: the minutes, the diagram, the action items. The user's own thoughts have nowhere to go — anything they type into the broadcast bar is dispatched to the agents, not kept for themselves.
What changed
A per-meeting note the user owns, beside the agents' outputs, with screenshot paste.
GET/PUT /meetings/{id}/note— the whole note in, the whole note out (no partial update to express, so no PATCH). Markdown, autosaved, capped atMAX_NOTE_CHARS(100k — a human typing for at most the 4 hoursMAX_SESSION_DURATIONallows).POST /meetings/{id}/note/images— one pasted screenshot per multipart request. The body is validated by hand (size cap 8 MiB per image, 200 images per meeting) and the content is signature-sniffed (PNG/JPEG/GIF/WebP magic bytes), never trusted from the filename or Content-Type.safe_agent_id(id) + extfilename, and_SAFE_AGENT_ID_REcan produce neither a leading underscore nor a path separator — so_note.mdis unreachable by any agent because of the underscore (note.mdwould be a legal agent id), andimages/is unreachable because it is a directory.test_note_filename_is_unreachable_by_any_agentasserts this through the validator, so loosening the regex fails there rather than silently handing an agent the user's writing.NoteSidebar(edit/preview, autosave state, image paste with the meeting's elapsed time), opened from the meeting's overflow menu. It joins the existing one-side-panel-at-a-time rule (tasks / translation / note close each other), matching the overflow-menu shape feat(meetings): let the user edit an agent's minutes #5740 landed.Tests
test/test_meetings_note.py— store containment, unsafe-id refusal, the HTTP contract (cap accepted at the boundary, oversize 413, no redaction on the user's own text).test/test_meetings_note_images.py— the two unreachability properties, the by-hand multipart validation, signature sniffing per format, the per-meeting image budget.website/src/test/MeetingsNote.test.tsx(28 tests) — sidebar, autosave, paste.Local runs (rebased onto current
main): backend meetings suite 317 green,mypy(1209 files),flake8,isort, and the black ratchet green; websitetsc -b+ vite build green, meetings vitest files green (MeetingsNote,MeetingsApiClient,MeetingsSessionLogic, 122 tests). The full website vitest suite was not re-run locally; CI covers it.Manual verification
Not yet performed against a live meeting; the paste flow was exercised only through the component tests.
Related Issues
Part of the meetings feature stack split from the
feat/meetnotebranch. Independent of the other PRs in the stack (#5738, #5741, #2190).no linked issue: feature work from the meetings stack; no tracked issue exists for it.