feat(meetings): translate the transcript line by line - #5739
Conversation
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All counts verified. Composing the review now. First-Principles-Verdict: CONCERNS A third hand-rolled ephemeral What this change shipsIntent: let someone follow a meeting held in a language they don't speak, translated line by line in a side panel. ADDITION.
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 106b432 |
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS Per-line ephemeral kiro-cli sessions stack process-spawn overhead onto a latency-constrained loop, and real-model pacing is still unvalidated — otherwise a sound, well-contained design. Watch
Suggestions
[DESIGN-REVIEWED] 106b432 |
UX Review (Fable 5, fork) — 🟡 CONCERNSUX-level review of UX-Verdict: CONCERNS Both the meeting's mandatory closing action and the new translation feature now live behind a hidden "…" menu — findability, not the panel itself, is the risk. Watch
[UX-REVIEWED] 106b432 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsI've verified both candidates against the actual code. Candidate 1 — confirmed. Candidate 2 — dropped. The (c) outcome is "the model may follow the injected instruction," a "might," not a definite wrong outcome; the guard is the standard best-effort delimiter+DATA pattern the rest of the app uses, output is redacted, and tools are blocked by No new Step 2 findings meet the bar. Advisory only — the internal FINDING — src/kiro_crew/apps/builtins/meetings/backend/domain/session.py:483 — typed/chat dispatches reach [OPUS-REVIEWED] 106b432 |
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: This PR has been sitting with failing CI and no recent author activity. I've assessed the blockers and they appear resolvable — I'll push fixes directly to this branch as a co-author. Assessment: Merge conflict is confined to 7 i18n locale JSON files (additive-key resolution), and the GPT review's two blocking UI findings come with prescribed fixes (responsive TranslationSidebar width; fold secondary MeetingView toolbar actions into an overflow menu). Plan: rebase, resolve locale conflicts, apply both prescribed UI fixes + spec updates, refresh screenshots, and disposition the advisory lanes. If you'd prefer I don't touch this PR, add the |
ad7b568 to
197b00e
Compare
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Pushed the drive-to-green fixes as a new head ( 1. Rebase onto current 2. GPT blocking finding (a) — fixed sidebar clips at 320px ( 3. GPT blocking finding (b) — toolbar exceeds the two-action cap ( 4. Side-panel mutual exclusion (caught by a local pre-push Opus-mirror review): with the translation panel now stacking below 5. Translation poll ladder (same local review): 6. Catalog key-order normalization (same local review): the three 7. Tests + evidence: Local gates on the final head: backend isort/flake8/mypy/black-ratchet green, |
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Dispositions for the GPT 5.6 Review findings on
|
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Dispositions for the Design Review concerns on
|
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Dispositions for the UX Review concerns on
|
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Dispositions for the First Principles Review items on
|
197b00e to
5bb7c27
Compare
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Dispositions for the GPT 5.6 Review findings on
Scope note from the focused verification, for the record: |
5bb7c27 to
106b432
Compare
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Disposition for the Opus 4.8 Review advisory finding on
|
bolichen97
left a comment
There was a problem hiding this comment.
Approved after a full-diff review (scope match, no out-of-scope files, security surface checked, tests verified non-vacuous). Review notes available on request.
A meeting whose participants do not share a language is hard to read afterwards. Each transcript line can now be translated on demand, cached per meeting so a line is translated once, and shown beside the original in a sidebar instead of replacing it. The target language is a per-meeting setting, and translation runs through the agent the meeting already uses. Original feature by Kai Mitsuzawa (kaizawa97). Review fixes by Kiro Crew: resolved the additive i18n locale conflicts from the rebase onto main, released the translation sidebar's fixed 340px width below `lg` (stacked with a bounded height, the shape TaskSidebar already uses so a 320px viewport no longer clips it), and moved the meeting toolbar's secondary actions (end-and-review, refresh, translation, action items) into an overflow menu so the row stays within the two-control cap. Second review round: append_translation now refuses to write when the meeting's metadata is gone, so a worker persistence racing delete_meeting can no longer recreate the deleted directory; and the client resets its cursor (and refetches from zero) when the server's document language changes, so a restarted document's initial lines are never skipped and the accumulator is keyed on the observed server language rather than live config. Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
106b432 to
133a2fd
Compare
bolichen97
left a comment
There was a problem hiding this comment.
Re-approving after conflict-resolution rebase (single dataclass conflict in meetings session.py: main's init_buffer/init_dropped fields and this PR's root/translations fields both kept; AST-verified; single commit, scope unchanged).
Problem / Motivation
A meeting's participants don't always share a language. Today the transcript is only available in whatever language was spoken; someone following along in another language has to leave the app to translate, line by line, losing the meeting while they do.
Why it matters
Live translation is the difference between "can attend" and "can participate" for mixed-language teams — and doing it inside the app means the translation benefits from the app's own dictionary corrections instead of translating STT mistakes.
What changed (motivation → approach → change)
Goal: translate each spoken line into a user-chosen language, shown in a side panel while the meeting runs — off by default, because it costs one model call per line.
backend/domain/translate.py— a bounded SEQUENTIAL per-meeting queue running one tool-less call onkirocrew-liteper line, ephemeral session destroyed after. Deliberately not anAgentQueuevariant (that one exists to batch 30 s for agent context; this exists to avoid batching). This is the app's first non-agent LLM path.MeetingSession.broadcast, not the dispatch route — broadcast is where text is already dictionary-corrected and past the noise gate. A mangled project noun mistranslates into something unrecognisable, and translated throat-clearing is worse than nothing.translations.json(translate.pyallowlisted as a non-egress redaction module insecurity_posture.py).GET …/translations?since=Nis cursor-paged; the client accumulates into a Map keyed by line number so aqueryFnrunning twice for one cursor (React Strict Mode) cannot duplicate lines. A failed line is persisted withtext: ""so the panel marks it rather than showing a gap indistinguishable from silence. Language change resets the document server-side and the accumulator client-side.GET /config(translation_languages, labels are endonyms) rather than hardcoded in the frontend — the backend validates the saved value, so it also publishes the accepted set.TranslationSidebar+ a Settings select (using the sharedSimpleSelect); polling runs only while the panel is open AND a language is set, at the active rate while live and the idle rate while paused/reviewing (the backend queue keeps draining while paused, so the panel would otherwise freeze mid-sentence).Review-driven changes (Kiro Crew, co-author), addressing the GPT review's two blocking findings and rebase fallout:
TranslationSidebarreleases its fixed 340px width belowlg—w-fullwith a bounded height (h-[42%] min-h-[260px]) when stacked,lg:w-[340px] lg:h-fullbeside the meeting — the same responsive shapeTaskSidebaralready uses, so a 320px viewport no longer clips the panel.DropdownMenu— five sibling buttons breached themax-two-buttons-per-rowcap and wrapped under width pressure; the row now holds the one primary status action plus the trigger, and every menu item keeps a full text label (newapps.meetings.meeting.moreActionskey, all 14 catalogs). The two side panels are mutually exclusive: stacked belowlg, their combined 260px height floors would squeeze the transcript out entirely.mainresolved additive key conflicts in 7 locale catalogs and normalized catalog key order to thesortDeepform the i18n tooling writes, so a future automated translate run carries no reordering churn.append_translationrefuses to write when the meeting no longer exists — the worker persists on a thread and could lose a race withdelete_meeting, silently recreating the deleted meeting's directory via_write_json'smkdir; both sides takemeta_transaction, so the new metadata guard is race-free (second GPT review round).website/scripts/capture-meetings-translation.mjs— a capture harness (real SPA, deterministic API fixtures, followingcapture-meetings-delete.mjs) that verifies the toolbar cap and the 320px sidebar bounding box, and produces the screenshots below.Tests
test/test_meetings_translation.py(52 tests) — the injection guard, the bounded queue (never blocks, never raises, drops with a count when the backlog fills), off-by-default, unknown-language-resolves-to-off, redaction of the model's answer, the cursor endpoint, and the delete-race guard (a worker write racingdelete_meetingcannot recreate the deleted directory).website/src/test/MeetingsTranslation.test.tsx— the Map-keyed accumulation (Strict-Mode idempotence), the reset on language change, the enabled-gating of the poll, the paused/reviewing idle-poll ladder, the two-control toolbar cap (secondary actions live in the overflow menu, not as row buttons), the side-panel mutual exclusion, the responsive sidebar shape (releases 340px belowlg, bounded height, divider turns with the layout), and the server-language cursor reset (refetch from zero, keyed on observed server language).Local runs: backend isort/flake8/mypy (1167 files) and black ratchet green;
test_meetings_translation.py51 passed and the meetings test family 606 passed; websitetsc, eslint,i18n:check, and the full vitest suite (25,907 passed) green.Manual verification
Exercised end-to-end against a live harness gateway (fake ACP backend):
PUT /config(translation_language), started a meeting, and dispatched two English lines.GET …/translations?since=0until both lines carried translated text produced by the per-line queue (one ephemeral, tool-less session per line), and confirmed the reported language surfaced asja/ 日本語.translations.json).The screenshots below come from the capture harness (real production SPA, stubbed API): the overflow menu open on a live meeting, the sidebar beside the meeting at
lg, and the stacked bounded panel at a 320px viewport. Still pending before merge sign-off: a pass against a production translation model — the harness backend returns a canned reply, so this proves the pipeline (queue → ephemeral session → cursor-paged endpoint → accumulator), not translation quality.Screenshots / video
Live meeting, dark theme, from
scripts/capture-meetings-translation.mjs.320px viewport: the panel stacks with a bounded height instead of clipping
Related Issues
Part of the meetings feature stack split from the
feat/meetnotebranch. A follow-up PR (a per-meeting note the user owns) depends on this one and will be submitted once this merges.no linked issue: feature work from the meetings stack, no filed issue tracks it.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)