Rewritten 2026-08-13. This was filed as "Spike 3/3: generate the playback page from artifacts". Review killed that framing: scripts/spike-media/build_page.pyalready generates the page from (payload.json, video). There is no spike here — the question it claimed to ask is answered.
What is left is small, and one part of it is a real problem the original made worse.
1. The generator hardcodes one video
build_page.py:348:
TEMPLATE.replace("__TITLE__",html.escape("Jonas – genomgång av tillbyggnad"))
Title, speaker name and domain must come from the payload or the CLI. Until then "it generalizes" is untested by construction.
2. /bygge still has no versioned source of truth — and it cannot get one here
The original acceptance said "generator committed here, artifacts gitignored", while #28's comment claimed this issue closes the source-of-truth gap. Those contradict: a generator with no committed inputs is a source of truth for nothing.
Then a harder constraint turned up while committing the toolchain: mathias/tapir is a public repo.IMG_1233.sv.srt and payload.json are a verbatim recording of a named person discussing a client's building project — private third-party content. Committing them here publishes them. This is the same class of data #28 names as the reason uploaded transcripts cannot share the transcripts table, so it should not be waved through for the prototype's own artifacts.
Private repo (or private submodule) for reference artifacts
One repo; keeps tapir public and the artifacts versioned
SOPS-encrypted blob in this repo
No new repo; git diff becomes useless on them
Accept no version control for the real artifacts; generalization proven only on synthetic fixtures
Free; the /bygge regeneration check becomes a manual one-off
Recommendation: private repo. Do not commit the real transcript to a public repo to satisfy a checkbox. Until this is decided, the durable copy lives outside git at ~/dev/.rescue/bygge-toolchain-2026-08-13/private/, which is not a plan, it is a stay of execution.
3. The video tradeoff in the original was a false dilemma — cut
The original asked whether a smaller encode would lose "the detail on a wall". Parsed from the containers:
codec
resolution
fps
size
bitrate
IMG_1233.mov
hvc1
720×1280
30
317 MiB
~11 Mbps
IMG_1233.web.mp4
avc1
720×1280
30
146 MiB
~5.1 Mbps
transcode.yaml uses scale='min(1280,iw)':-2, and iw is 720 — the filter is a no-op. It is HEVC→H.264 at identical resolution. The wall detail was never captured, and 5.1 Mbps is -preset veryfast on handheld constant-motion content, not a quality decision. The lever is the preset.
So measure instead of agonizing:
Re-encode at -preset slow -crf 28 and -crf 30, report MiB
Expected 20–45 MiB, i.e. 3–7× smaller with no perceptible loss at 720p
Correct #28 decision 5's storage math: a properly encoded hour is ~300–600 MB, not the ">2 GB" the original asserted by inheriting the bad encode
Unlike #29 and #30 this costs an hour and produces no new artifacts to be sunk-cost about. Do it whenever.
Acceptance
caveman: me watch video and see what was decided at that moment, without writing page by hand
Title, speaker name and domain are not hardcoded anywhere in the generator or the prompt — grep proves it
Page generated from fixtures/fake.analys.json + any video renders correctly — generalization shown on the synthetic fixture, which needs no privacy decision
(blocked on that decision)build_page.py payload.json IMG_1233.web.mp4 produces HTML whose embedded DATA object is byte-identical to the served page's, verified by diff
ADR-025 (honest state-aware status — the warn block is the same principle)
**Rewritten 2026-08-13.** This was filed as "Spike 3/3: generate the playback page from artifacts". Review killed that framing: `scripts/spike-media/build_page.py` **already** generates the page from `(payload.json, video)`. There is no spike here — the question it claimed to ask is answered.
What is left is small, and one part of it is a real problem the original made worse.
## 1. The generator hardcodes one video
`build_page.py:348`:
```python
TEMPLATE.replace("__TITLE__", html.escape("Jonas – genomgång av tillbyggnad"))
```
Title, speaker name and domain must come from the payload or the CLI. Until then "it generalizes" is untested by construction.
## 2. `/bygge` still has no versioned source of truth — and it cannot get one here
The original acceptance said *"generator committed here, artifacts gitignored"*, while #28's comment claimed this issue **closes** the source-of-truth gap. Those contradict: a generator with no committed inputs is a source of truth for nothing.
Then a harder constraint turned up while committing the toolchain: **`mathias/tapir` is a public repo.** `IMG_1233.sv.srt` and `payload.json` are a verbatim recording of a named person discussing a client's building project — private third-party content. Committing them here publishes them. This is the same class of data #28 names as the reason uploaded transcripts cannot share the `transcripts` table, so it should not be waved through for the prototype's own artifacts.
**DECISION NEEDED — blocks the "regenerate `/bygge` byte-identically" check, nothing else:**
| Option | Cost |
|---|---|
| Private repo (or private submodule) for reference artifacts | One repo; keeps tapir public and the artifacts versioned |
| SOPS-encrypted blob in this repo | No new repo; `git diff` becomes useless on them |
| Accept no version control for the real artifacts; generalization proven **only** on synthetic fixtures | Free; the `/bygge` regeneration check becomes a manual one-off |
Recommendation: private repo. Do **not** commit the real transcript to a public repo to satisfy a checkbox. Until this is decided, the durable copy lives outside git at `~/dev/.rescue/bygge-toolchain-2026-08-13/private/`, which is not a plan, it is a stay of execution.
## 3. The video tradeoff in the original was a false dilemma — cut
The original asked whether a smaller encode would lose "the detail on a wall". Parsed from the containers:
| | codec | resolution | fps | size | bitrate |
|---|---|---|---|---|---|
| `IMG_1233.mov` | `hvc1` | **720×1280** | 30 | 317 MiB | ~11 Mbps |
| `IMG_1233.web.mp4` | `avc1` | **720×1280** | 30 | 146 MiB | ~5.1 Mbps |
`transcode.yaml` uses `scale='min(1280,iw)':-2`, and `iw` is 720 — **the filter is a no-op.** It is HEVC→H.264 at identical resolution. The wall detail was never captured, and 5.1 Mbps is `-preset veryfast` on handheld constant-motion content, not a quality decision. The lever is the preset.
So measure instead of agonizing:
- Re-encode at `-preset slow -crf 28` and `-crf 30`, report MiB
- Expected 20–45 MiB, i.e. 3–7× smaller with no perceptible loss at 720p
- **Correct #28 decision 5's storage math**: a properly encoded hour is ~300–600 MB, not the ">2 GB" the original asserted by inheriting the bad encode
## Not blocked on #32
Unlike #29 and #30 this costs an hour and produces no new artifacts to be sunk-cost about. Do it whenever.
## Acceptance
- [ ] `caveman: me watch video and see what was decided at that moment, without writing page by hand`
- [ ] Title, speaker name and domain are **not hardcoded anywhere** in the generator or the prompt — grep proves it
- [ ] Page generated from `fixtures/fake.analys.json` + any video renders correctly — generalization shown on the **synthetic** fixture, which needs no privacy decision
- [ ] Re-encode measured at `-preset slow` `-crf 28`/`-crf 30`; numbers posted; #28 decision 5's storage estimate corrected
- [ ] Red-first tests on cue→card timestamp mapping and the seek index
- [ ] Visual result checked by eye, and **said** to be checked by eye
- [ ] Artifact-custody decision above recorded in #28
- [ ] *(blocked on that decision)* `build_page.py payload.json IMG_1233.web.mp4` produces HTML whose embedded `DATA` object is **byte-identical** to the served page's, verified by `diff`
## Refs
- `scripts/spike-media/` (committed 2026-08-13), #28 (Slice 5, decision 5), #29, #30, #32
- ADR-025 (honest state-aware status — the `warn` block is the same principle)
mathias
changed title from Spike 3/3: generate the synced playback page from artifacts instead of hand-building it to Chore: parametrize build_page.py and prove the page generalizes (not a spike)2026-08-13 09:39:29 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Rewritten 2026-08-13. This was filed as "Spike 3/3: generate the playback page from artifacts". Review killed that framing:
scripts/spike-media/build_page.pyalready generates the page from(payload.json, video). There is no spike here — the question it claimed to ask is answered.What is left is small, and one part of it is a real problem the original made worse.
1. The generator hardcodes one video
build_page.py:348:Title, speaker name and domain must come from the payload or the CLI. Until then "it generalizes" is untested by construction.
2.
/byggestill has no versioned source of truth — and it cannot get one hereThe original acceptance said "generator committed here, artifacts gitignored", while #28's comment claimed this issue closes the source-of-truth gap. Those contradict: a generator with no committed inputs is a source of truth for nothing.
Then a harder constraint turned up while committing the toolchain:
mathias/tapiris a public repo.IMG_1233.sv.srtandpayload.jsonare a verbatim recording of a named person discussing a client's building project — private third-party content. Committing them here publishes them. This is the same class of data #28 names as the reason uploaded transcripts cannot share thetranscriptstable, so it should not be waved through for the prototype's own artifacts.DECISION NEEDED — blocks the "regenerate
/byggebyte-identically" check, nothing else:git diffbecomes useless on them/byggeregeneration check becomes a manual one-offRecommendation: private repo. Do not commit the real transcript to a public repo to satisfy a checkbox. Until this is decided, the durable copy lives outside git at
~/dev/.rescue/bygge-toolchain-2026-08-13/private/, which is not a plan, it is a stay of execution.3. The video tradeoff in the original was a false dilemma — cut
The original asked whether a smaller encode would lose "the detail on a wall". Parsed from the containers:
IMG_1233.movhvc1IMG_1233.web.mp4avc1transcode.yamlusesscale='min(1280,iw)':-2, andiwis 720 — the filter is a no-op. It is HEVC→H.264 at identical resolution. The wall detail was never captured, and 5.1 Mbps is-preset veryfaston handheld constant-motion content, not a quality decision. The lever is the preset.So measure instead of agonizing:
-preset slow -crf 28and-crf 30, report MiBNot blocked on #32
Unlike #29 and #30 this costs an hour and produces no new artifacts to be sunk-cost about. Do it whenever.
Acceptance
caveman: me watch video and see what was decided at that moment, without writing page by handfixtures/fake.analys.json+ any video renders correctly — generalization shown on the synthetic fixture, which needs no privacy decision-preset slow-crf 28/-crf 30; numbers posted; #28 decision 5's storage estimate correctedbuild_page.py payload.json IMG_1233.web.mp4produces HTML whose embeddedDATAobject is byte-identical to the served page's, verified bydiffRefs
scripts/spike-media/(committed 2026-08-13), #28 (Slice 5, decision 5), #29, #30, Pre-spike: does anyone re-open /bygge? (blocks #29 and #30) (#32)warnblock is the same principle)Spike 3/3: generate the synced playback page from artifacts instead of hand-building itto Chore: parametrize build_page.py and prove the page generalizes (not a spike)