Public: anyone can read this; the crew can change it.

A subtitle file (.srt, .vtt) renders as a timed transcript #10334

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A `.srt` or `.vtt` file now opens as a two-column transcript, start time and text, instead of plain source. I ran the new `test/render-subtitles.test.ts`, `test/render-kinds.test.ts` and `test/render-csv.test.ts` (the last because `csv.ts` is the renderer I copied), plus `tsc --noEmit`, the gate and the design lint. All passed. I did not run the full suite; `scripts/fast.ts` reported 1392 files, o…

shipped production is serving it deploy f21b126

opened this

A .srt or .vtt file now opens as a two-column transcript, start time and text, instead of plain source. I ran the new test/render-subtitles.test.ts, test/render-kinds.test.ts and test/render-csv.test.ts (the last because csv.ts is the renderer I copied), plus tsc --noEmit, the gate and the design lint. All passed. I did not run the full suite; scripts/fast.ts reported 1392 files, over its 5-minute budget, so I left that to the forge's check.

  • Kind and renderer: .srt and .vtt are a new subtitles kind in src/web/render/kinds.ts, with a 5 MB render cap. src/web/render/subtitles.ts draws them and is registered in renderers.ts.
  • Parsing: SRT and VTT timings are accepted in either file, with a comma or a dot before the milliseconds and the hour optional. VTT NOTE, STYLE and REGION blocks are skipped.
  • Text: tags such as <i>, <v Name> and <00:02.000>, and {\an8} style overrides, are stripped, then the text is escaped. A bare 1 < 2 is kept because only < followed directly by a letter, digit or / counts as a tag.
  • A bad cue: the rest of the file still renders. Each failure gets one line naming the cue and its line number, up to 5, then "N more cues could not be read."
  • A file with no cues: it falls back to the source view.
  • Limit I added: at most 5,000 cues are drawn, with a note that the rest is in the raw file. There is no ?all=1 for it, and I did not add sorting or filtering.
  • Tests: test/render-subtitles.test.ts covers an SRT and a VTT fixture, a cue containing <script>, and a malformed cue. I added .srt and .vtt samples to test/render-kinds.test.ts.
  • Push: the pre-push hook refused the first push because no commit said how to try it, so I added an empty "Try:" commit.

To try it, open any .srt or .vtt file on a repository's blob page.

🤖 Generated with Claude Code

Closes #10287

commented

A .srt or .vtt file now opens as a two-column transcript, start time and text, instead of plain source. I ran the new test/render-subtitles.test.ts, test/render-kinds.test.ts and test/render-csv.test.ts (the last because csv.ts is the renderer I copied), plus tsc --noEmit, the gate and the design lint. All passed. I did not run the full suite; scripts/fast.ts reported 1392 files, over its 5-minute budget, so I left that to the forge's check.

  • Kind and renderer: .srt and .vtt are a new subtitles kind in src/web/render/kinds.ts, with a 5 MB render cap. src/web/render/subtitles.ts draws them and is registered in renderers.ts.
  • Parsing: SRT and VTT timings are accepted in either file, with a comma or a dot before the milliseconds and the hour optional. VTT NOTE, STYLE and REGION blocks are skipped.
  • Text: tags such as <i>, <v Name> and <00:02.000>, and {\an8} style overrides, are stripped, then the text is escaped. A bare 1 < 2 is kept because only < followed directly by a letter, digit or / counts as a tag.
  • A bad cue: the rest of the file still renders. Each failure gets one line naming the cue and its line number, up to 5, then "N more cues could not be read."
  • A file with no cues: it falls back to the source view.
  • Limit I added: at most 5,000 cues are drawn, with a note that the rest is in the raw file. There is no ?all=1 for it, and I did not add sorting or filtering.
  • Tests: test/render-subtitles.test.ts covers an SRT and a VTT fixture, a cue containing <script>, and a malformed cue. I added .srt and .vtt samples to test/render-kinds.test.ts.
  • Push: the pre-push hook refused the first push because no commit said how to try it, so I added an empty "Try:" commit.

To try it, open any .srt or .vtt file on a repository's blob page.

🤖 Generated with Claude Code

Changes requested — before the latest push

The renderer works and the tests are good, but three things block it.

  1. Both doors: the terminal workspace (src/tui/screens/workspace.ts) renders .csv as a table, and .srt/.vtt there still open as raw source. Add the subtitle transcript to the TUI file view, sharing parseSubtitles, with a test.
  2. No docs/features/subtitles.md how-to (csv-view.md is the pattern).
  3. Design: the transcript is wrapped in a card (boxes around a thing, principles 6 and 7). Drop the card and let the table sit on the page with hairline row ends. subs-view, subs-failed and num have no CSS (.num is only styled under .csv-view); use classes that exist or add the rules in your area doc, not docs/DESIGN.md.

Also: failures are listed above the table, pushing the transcript down and shifting layout; put the notes below it or in one summary line. The failure wording ('unreadable timing') is fine but say what to do, e.g. 'check the arrow line'.

commented

All three review points are addressed and pushed. tsc, scripts/gate.ts, scripts/design-lint.ts and the render-subtitles, render-kinds and tui-workspace tests pass. I did not run the full suite; the forge's check does that on the push.

What changed

  • Terminal workspace: .srt and .vtt now open as an aligned Start/Text table, built from the same parseSubtitles the web uses. The existing s key flips between the table and the source. The status line reads N cues, plus · M unreadable, check their arrow lines when some cues failed. A new test in test/tui-workspace.test.ts covers the table, the status line and s.
  • Docs: docs/features/subtitles.md is the how-to, modelled on csv-view.md. docs/design/areas/subtitles.md records that no new component class was added.
  • Design:

- The card is gone. The transcript is now a bare table table-dense, as you asked. - The num and subs-failed classes are removed. .table .num is in fact styled globally, but it right-aligns, which is wrong for start times, so I dropped it. - subs-view stays on the table as a hook, with no CSS.

  • Failure notes: one muted small line below the table. It gives the count, the first bad cue and what to do ("check the arrow line"). A missing arrow line gets a similar hint. The old per-cue list and FAILURE_CAP are gone. The test now checks that the note comes after </table> and that no card is rendered.

What I chose not to do

  • The terminal table lists only the first unreadable cue's count, not each failure by name. The s source view is where you find them.
  • The web renderer still draws at most 5,000 cues, and the terminal table uses the same cap.

How to try it: open any .srt or .vtt file in a repository on the preview, or in the terminal workspace, and press s there to see the source.

commented

I merged main into the branch and pushed it (f813b3182). The first push was rejected by a pre-receive hook, and a plain retry went through, so I don't know why it failed.

  • Conflict: the only one was in src/web/render/renderers.ts. main added the archive renderer and this branch adds subtitles, so I kept both entries in the renderer table.
  • Checks run: bun x tsc --noEmit was clean, and bun scripts/gate.ts printed no failure, though I only looked at its last lines. I ran 178 render, subtitle and archive tests across 27 files and all passed.
  • Not run: I didn't run the full suite, because scripts/fast.ts said the diff was too broad to name tests. The forge's check on this push will run it.
Approved

Earlier asks are met. The terminal workspace now draws the same cues through the shared parseSubtitles, with s flipping to the source and a test for it. docs/features/subtitles.md holds the how-to and README is untouched. The card is gone, so the transcript is a bare dense table with one muted note under it. The merge of main kept both the archive and subtitles renderers. Cue text is stripped of tags and then escaped, and the tests cover a script cue and a malformed cue.

Two things I would do next:

  1. Non-blocking: the terminal status line only counts unreadable cues. Name the first one and its line, as the web note does.
  2. Non-blocking: add a way past the 5,000-cue cap, such as ?all=1, or say in the note where the rest is.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs