Files
2026-07-13 12:33:58 +08:00

574 lines
29 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- Copyright 2026 Anthropic PBC · SPDX-License-Identifier: Apache-2.0 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The buy-in doc — Know your unknowns</title>
<style>
:root {
--ivory:#FAF9F5; --paper:#FFFFFF; --slate:#141413; --clay:#D97757; --clay-d:#B85C3E;
--oat:#E3DACC; --olive:#788C5D; --g100:#F0EEE6; --g200:#E6E3DA; --g300:#D1CFC5;
--g500:#87867F; --g700:#3D3D3A;
--serif: ui-serif, Georgia, "Times New Roman", Times, serif;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--ivory); color: var(--slate);
font-family: var(--sans); line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
.wrap { max-width: 980px; margin: 0 auto; padding: 0 20px; }
h1, h2, h3 { font-family: var(--serif); font-weight: 500; letter-spacing: -0.015em; }
/* ---------- page header ---------- */
header.page { padding: 56px 0 40px; border-bottom: 1.5px solid var(--g300); position: relative; }
.eyebrow {
font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--g500); margin-bottom: 18px;
display: flex; align-items: center; gap: 12px;
}
.eyebrow::before { content: ""; width: 24px; height: 1.5px; background: var(--clay); flex: none; }
.backlink {
position: absolute; top: 60px; right: 0;
font-family: var(--mono); font-size: 12px; color: var(--g500); text-decoration: none;
}
.backlink:hover { color: var(--clay-d); }
header.page h1 { font-size: clamp(34px, 5vw, 52px); line-height: 1.08; margin: 0 0 10px; }
.lede { font-size: 16.5px; color: var(--g700); max-width: 640px; }
/* ---------- prompt box ---------- */
.promptcard {
background: var(--paper); border: 1.5px solid var(--g300); border-radius: 14px;
padding: 22px 26px; margin: 30px 0 0; position: relative;
}
.promptcard .label {
font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--clay); margin-bottom: 12px;
}
.promptcard blockquote {
font-family: var(--serif); font-style: italic; font-size: 17.5px;
line-height: 1.5; color: var(--slate); max-width: 720px;
}
.copybtn {
position: absolute; top: 18px; right: 18px;
font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em;
background: var(--g100); color: var(--g700); border: 1.5px solid var(--g300);
border-radius: 8px; padding: 6px 12px; cursor: pointer;
}
.copybtn:hover { border-color: var(--clay); color: var(--clay-d); }
.copybtn.done { background: var(--olive); border-color: var(--olive); color: #fff; }
/* ---------- divider ---------- */
.divider {
display: flex; align-items: center; gap: 16px; margin: 44px 0 36px;
font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--g500);
}
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--g300); }
/* ================= ARTIFACT ================= */
.artifact {
background: var(--paper); border: 1.5px solid var(--g300); border-radius: 14px;
padding: clamp(22px, 4vw, 44px); margin-bottom: 48px;
}
.doc-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 6px; }
.doc-eyebrow {
font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--g500);
display: flex; align-items: center; gap: 10px;
}
.doc-eyebrow::before { content: ""; width: 24px; height: 1.5px; background: var(--clay); flex: none; }
.timechip {
font-family: var(--mono); font-size: 11.5px; color: var(--g700);
background: var(--oat); border-radius: 999px; padding: 5px 13px; white-space: nowrap;
display: inline-flex; align-items: center; gap: 7px;
}
.timechip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--clay); }
.artifact h2.doc-title { font-size: clamp(26px, 3.6vw, 36px); line-height: 1.12; margin: 10px 0 4px; }
.doc-sub { color: var(--g500); font-size: 14px; margin-bottom: 26px; }
.doc-sub strong { color: var(--g700); font-weight: 600; }
.artifact h3.sec {
font-size: 21px; margin: 38px 0 14px; padding-top: 26px;
border-top: 1.5px solid var(--g200);
display: flex; align-items: baseline; gap: 10px;
}
.artifact h3.sec .n { font-family: var(--mono); font-size: 12px; color: var(--clay); font-weight: 400; }
/* ---------- demo player ---------- */
.demo-shell { position: relative; }
.demo {
background: var(--g100); border: 1.5px solid var(--g300); border-radius: 12px;
overflow: hidden; user-select: none;
}
.bl-titlebar {
display: flex; align-items: center; gap: 8px;
background: var(--paper); border-bottom: 1.5px solid var(--g200);
padding: 9px 14px;
}
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--g300); flex: none; }
.bl-name { font-family: var(--mono); font-size: 11px; color: var(--g500); margin-left: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bl-body { display: grid; grid-template-columns: 1fr 232px; min-height: 268px; }
@media (max-width: 620px) { .bl-body { grid-template-columns: 1fr; } .bl-panel { border-left: none !important; border-top: 1.5px solid var(--g200); } }
/* video pane */
.bl-stage { position: relative; padding: 16px 16px 12px; display: flex; flex-direction: column; }
.bl-video {
flex: 1; min-height: 148px; border-radius: 8px; position: relative;
background:
radial-gradient(120px 80px at 68% 34%, #4a4b45 0%, transparent 70%),
linear-gradient(160deg, #3D3D3A 0%, #232322 100%);
border: 1.5px solid var(--g300);
}
.bl-play {
position: absolute; left: 12px; bottom: 10px; width: 0; height: 0;
border-left: 11px solid rgba(250,249,245,.85); border-top: 7px solid transparent; border-bottom: 7px solid transparent;
}
.bl-tc { position: absolute; right: 12px; bottom: 8px; font-family: var(--mono); font-size: 10.5px; color: rgba(250,249,245,.7); }
.bl-pin {
position: absolute; top: -7px; width: 14px; height: 14px; border-radius: 50% 50% 50% 2px;
background: var(--g500); transform: rotate(45deg); transition: background .25s;
}
.bl-pin.on { background: var(--clay); }
.bl-timeline { position: relative; height: 26px; margin-top: 14px; }
.bl-track { position: absolute; left: 0; right: 0; top: 11px; height: 4px; border-radius: 2px; background: var(--g300); }
.bl-track .fill { width: 34%; height: 100%; border-radius: 2px; background: var(--g500); }
/* annotation panel */
.bl-panel { background: var(--paper); border-left: 1.5px solid var(--g200); padding: 12px 12px 10px; display: flex; flex-direction: column; }
.bl-panel-head {
font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--g500); display: flex; justify-content: space-between; align-items: center; margin-bottom: 9px;
}
.bl-count { color: var(--clay-d); }
.bl-ann {
display: flex; gap: 9px; align-items: flex-start; padding: 8px 8px;
border-radius: 8px; border: 1.5px solid transparent; transition: background .25s, border-color .25s;
}
.bl-ann.sel { background: var(--g100); border-color: var(--g300); }
.bl-check {
width: 15px; height: 15px; border-radius: 4px; border: 1.5px solid var(--g300);
background: var(--paper); flex: none; margin-top: 2px; position: relative; transition: background .2s, border-color .2s;
}
.bl-ann.sel .bl-check { background: var(--clay); border-color: var(--clay); }
.bl-ann.sel .bl-check::after {
content: ""; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px;
border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.bl-ann .who { font-size: 11.5px; font-weight: 600; line-height: 1.35; }
.bl-ann .who .t { font-family: var(--mono); font-weight: 400; font-size: 10px; color: var(--g500); margin-left: 5px; }
.bl-ann .txt { font-size: 11px; color: var(--g700); line-height: 1.4; }
.bl-export {
margin-top: auto; align-self: stretch; text-align: center;
font-family: var(--sans); font-size: 12.5px; font-weight: 600; color: #fff;
background: var(--slate); border: none; border-radius: 8px; padding: 9px 0;
transition: background .2s, transform .12s; pointer-events: none;
}
.bl-export.armed { background: var(--clay); }
.bl-export.pressed { background: var(--clay-d); transform: scale(.965); }
/* progress overlay */
.bl-progress {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
background: rgba(20,20,19,.38); opacity: 0; pointer-events: none; transition: opacity .3s;
}
.bl-progress.show { opacity: 1; }
.bl-prog-card {
background: var(--paper); border-radius: 12px; padding: 20px 24px; width: min(300px, 84%);
box-shadow: 0 12px 32px rgba(20,20,19,.22);
}
.bl-prog-card .pt { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.bl-prog-card .ps { font-family: var(--mono); font-size: 10.5px; color: var(--g500); margin-bottom: 12px; min-height: 14px; }
.bl-bar { height: 6px; border-radius: 3px; background: var(--g200); overflow: hidden; }
.bl-bar .b { width: 0%; height: 100%; background: var(--clay); border-radius: 3px; transition: width 1.7s cubic-bezier(.3,.7,.3,1); }
/* toast */
.bl-toast {
position: absolute; left: 50%; bottom: 14px; transform: translate(-50%, 16px);
background: var(--slate); color: var(--ivory); border-radius: 10px;
padding: 10px 14px; display: flex; align-items: center; gap: 10px;
opacity: 0; transition: opacity .35s, transform .35s; max-width: 92%;
box-shadow: 0 10px 26px rgba(20,20,19,.3);
}
.bl-toast.show { opacity: 1; transform: translate(-50%, 0); }
.bl-tick {
width: 18px; height: 18px; border-radius: 50%; background: var(--olive); flex: none; position: relative;
}
.bl-tick::after {
content: ""; position: absolute; left: 6px; top: 3.5px; width: 4px; height: 8px;
border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.bl-toast .m { font-size: 12px; white-space: nowrap; }
.bl-chip {
font-family: var(--mono); font-size: 10.5px; background: rgba(250,249,245,.14);
border: 1px solid rgba(250,249,245,.25); border-radius: 6px; padding: 3px 8px; white-space: nowrap;
}
.demo-caption {
display: flex; justify-content: space-between; align-items: center; gap: 12px;
margin-top: 10px; font-family: var(--mono); font-size: 11px; color: var(--g500);
}
.replay {
font-family: var(--mono); font-size: 11px; background: none; border: 1.5px solid var(--g300);
border-radius: 8px; color: var(--g700); padding: 5px 12px; cursor: pointer;
}
.replay:hover { border-color: var(--clay); color: var(--clay-d); }
/* ---------- pitch ---------- */
.pitch { font-family: var(--serif); font-size: 17.5px; line-height: 1.6; max-width: 46em; }
.pitch strong { font-weight: 600; }
/* ---------- objections ---------- */
.obj { border: 1.5px solid var(--g200); border-radius: 10px; margin-bottom: 10px; overflow: hidden; }
.obj summary {
list-style: none; cursor: pointer; padding: 12px 16px; display: flex; gap: 12px; align-items: baseline;
font-weight: 600; font-size: 14.5px;
}
.obj summary::-webkit-details-marker { display: none; }
.obj summary .q { font-family: var(--mono); font-size: 11px; color: var(--clay); flex: none; }
.obj summary::after { content: "+"; margin-left: auto; font-family: var(--mono); color: var(--g500); flex: none; }
.obj[open] summary::after { content: ""; }
.obj[open] summary { border-bottom: 1.5px solid var(--g200); background: var(--g100); }
.obj .a { padding: 13px 16px 14px 16px; font-size: 14px; color: var(--g700); }
.obj .ref {
display: inline-block; margin-top: 8px; font-family: var(--mono); font-size: 11.5px;
color: var(--clay-d); text-decoration: none; border-bottom: 1px dashed var(--g300);
}
.obj .ref:hover { border-bottom-color: var(--clay-d); }
/* ---------- spec table ---------- */
.tblwrap { overflow-x: auto; border: 1.5px solid var(--g200); border-radius: 10px; }
table.spec { width: 100%; border-collapse: collapse; font-size: 13.5px; min-width: 560px; }
table.spec th, table.spec td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--g200); vertical-align: top; }
table.spec tr:last-child td { border-bottom: none; }
table.spec th {
font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--g500); font-weight: 400; background: var(--g100);
}
table.spec td:first-child { font-weight: 600; white-space: nowrap; width: 140px; }
table.spec code, .a code, .risk code, .need code {
font-family: var(--mono); font-size: 0.88em; background: var(--g100);
border: 1px solid var(--g200); border-radius: 4px; padding: 1px 5px; white-space: nowrap;
}
.specref { font-family: var(--mono); font-size: 11px; color: var(--g500); }
/* ---------- risk + needs cards ---------- */
.cardgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .cardgrid { grid-template-columns: 1fr; } }
.risk, .need {
border: 1.5px solid var(--g300); border-radius: 12px; padding: 18px 20px; background: var(--ivory);
}
.risk { border-left: 4px solid var(--clay); }
.need { border-left: 4px solid var(--olive); }
.risk h4, .need h4 {
font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.1em; text-transform: uppercase;
font-weight: 400; margin-bottom: 12px;
}
.risk h4 { color: var(--clay-d); }
.need h4 { color: var(--olive); }
.risk ul { list-style: none; }
.risk li { font-size: 13.5px; color: var(--g700); padding-left: 18px; position: relative; margin-bottom: 9px; }
.risk li::before { content: ""; position: absolute; left: 2px; top: 8px; width: 7px; height: 2px; background: var(--clay); }
.risk li strong { color: var(--slate); }
.approver { display: flex; gap: 11px; align-items: flex-start; padding: 9px 0; border-bottom: 1px solid var(--g200); }
.approver:last-child { border-bottom: none; }
.avatar {
width: 30px; height: 30px; border-radius: 50%; flex: none;
background: var(--oat); color: var(--clay-d);
font-family: var(--mono); font-size: 11px; display: flex; align-items: center; justify-content: center;
}
.approver .info { font-size: 13px; line-height: 1.4; }
.approver .info b { display: block; font-size: 13.5px; }
.approver .info .role { font-family: var(--mono); font-size: 10.5px; color: var(--g500); }
.approver .info .asks { color: var(--g700); margin-top: 2px; }
.bottomline {
margin-top: 34px; padding: 16px 20px; background: var(--oat); border-radius: 10px;
font-family: var(--serif); font-size: 16px;
}
footer {
padding: 26px 0 56px; font-size: 13px; color: var(--g500);
border-top: 1.5px solid var(--g300);
}
footer a { color: var(--clay-d); }
</style>
</head>
<body>
<div class="wrap">
<header class="page">
<a class="backlink" href="index.html">&larr; All examples</a>
<div class="eyebrow">Know your unknowns &middot; Post-implementation</div>
<h1>The buy-in doc</h1>
<p class="lede">After the work is done, the last unknown is other people — ask Claude to package the prototype, spec, and notes into one skimmable doc that answers objections before they're raised.</p>
<div class="promptcard">
<div class="label">The prompt</div>
<button class="copybtn" id="copyBtn">copy prompt</button>
<blockquote id="promptText">&ldquo;Package the prototype, the spec, and the implementation notes into a single doc I can drop in Slack to get buy-in on shipping annotation export. Lead with the demo.&rdquo;</blockquote>
</div>
</header>
<div class="divider">What Claude produced</div>
<!-- ================= ARTIFACT ================= -->
<article class="artifact">
<div class="doc-top">
<div class="doc-eyebrow">Ship proposal &middot; Acme</div>
<span class="timechip">90-second read</span>
</div>
<h2 class="doc-title">Annotation export is ready to ship</h2>
<p class="doc-sub">Prototype, spec, and rollout plan in one place. <strong>Ask:</strong> four sign-offs by Friday, flag ramp starts Monday.</p>
<!-- 1 · DEMO -->
<h3 class="sec"><span class="n">01</span> Watch it work</h3>
<div class="demo-shell">
<div class="demo" id="demo" aria-label="Animated mock of the annotation export flow in Acme">
<div class="bl-titlebar">
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
<span class="bl-name">Acme — q3_launch_teaser_v4.mp4 · Review</span>
</div>
<div class="bl-body">
<div class="bl-stage">
<div class="bl-video">
<span class="bl-play"></span>
<span class="bl-tc">00:42 / 02:05</span>
</div>
<div class="bl-timeline">
<div class="bl-track"><div class="fill"></div></div>
<span class="bl-pin" id="pin1" style="left: 18%;"></span>
<span class="bl-pin" id="pin2" style="left: 41%;"></span>
<span class="bl-pin" id="pin3" style="left: 73%;"></span>
</div>
</div>
<div class="bl-panel">
<div class="bl-panel-head"><span>Annotations</span><span class="bl-count" id="selCount">0 selected</span></div>
<div class="bl-ann" id="ann1">
<span class="bl-check"></span>
<div>
<div class="who">Maya R. <span class="t">00:23</span></div>
<div class="txt">Logo lands too late — pull in 8 frames?</div>
</div>
</div>
<div class="bl-ann" id="ann2">
<span class="bl-check"></span>
<div>
<div class="who">Devon P. <span class="t">00:51</span></div>
<div class="txt">Color shift here vs. the master grade</div>
</div>
</div>
<div class="bl-ann" id="ann3">
<span class="bl-check"></span>
<div>
<div class="who">Maya R. <span class="t">01:30</span></div>
<div class="txt">Approved — final CTA reads great</div>
</div>
</div>
<button class="bl-export" id="exportBtn" tabindex="-1">Export selected</button>
</div>
</div>
<div class="bl-progress" id="progress">
<div class="bl-prog-card">
<div class="pt">Exporting 3 annotations</div>
<div class="ps" id="progStatus">Collecting annotations…</div>
<div class="bl-bar"><div class="b" id="progBar"></div></div>
</div>
</div>
<div class="bl-toast" id="toast">
<span class="bl-tick"></span>
<span class="m">Export ready</span>
<span class="bl-chip">q3_teaser_notes.srt · 3 cues</span>
</div>
</div>
<div class="demo-caption">
<span id="demoState">select &rarr; export &rarr; done · loops automatically</span>
<button class="replay" id="replayBtn">&#8635; replay</button>
</div>
</div>
<!-- 2 · PITCH -->
<h3 class="sec"><span class="n">02</span> The pitch</h3>
<p class="pitch">Reviewers leave feedback in Acme, then retype it into email, Notion, and edit-bay software — <strong>the single most-cited gap in churn interviews (41% of departed teams)</strong>. Annotation export turns any selection of comments and drawings into SRT, CSV, or PDF in one click, using infrastructure we already run. It's built, flagged, load-tested to 12k annotations per session, and reversible in one toggle. The only thing between here and shipped is the four sign-offs below.</p>
<!-- 3 · OBJECTIONS -->
<h3 class="sec"><span class="n">03</span> What reviewers will ask</h3>
<details class="obj" open>
<summary><span class="q">Q1</span>Can this leak internal-only comments to a client?</summary>
<div class="a">No. Export runs through the same visibility resolver as the review page: annotations marked <code>internal</code> are excluded unless the exporter holds <code>workspace:admin</code> and explicitly opts in via a labeled checkbox. Covered by 14 permission-matrix tests.
<br><a class="ref" href="#spec">&#8599; spec &sect;4.2 — Visibility &amp; permissions</a></div>
</details>
<details class="obj">
<summary><span class="q">Q2</span>What happens on a monster session — thousands of annotations?</summary>
<div class="a">Export paginates at 500 annotations per batch and streams the file. Stress run against the largest real session in prod (a 12,400-annotation agency review) finished in 3.1s; p95 for typical sessions is 340ms.
<br><a class="ref" href="#spec">&#8599; implementation notes, 06-24 — load test results</a></div>
</details>
<details class="obj">
<summary><span class="q">Q3</span>Why file formats instead of a public API?</summary>
<div class="a">SRT + CSV + PDF cover 83% of the 212 support tickets tagged <code>export-request</code> over the last two quarters. An API is the right phase 2 — it needs auth scoping work this doesn't. Files unblock users now without foreclosing the API design.
<br><a class="ref" href="#spec">&#8599; metric — support tag analysis, Q1&ndash;Q2</a></div>
</details>
<details class="obj">
<summary><span class="q">Q4</span>Does this add new infrastructure we have to operate?</summary>
<div class="a">Zero new services. SRT/CSV render inline in the existing worker pool (<code>services/render-workers</code>); PDF reuses the report service already generating weekly digests. No new queues, no new datastore, no schema migrations.
<br><a class="ref" href="#spec">&#8599; spec &sect;5.1 — Architecture</a></div>
</details>
<details class="obj">
<summary><span class="q">Q5</span>What's the compliance story for exported customer content?</summary>
<div class="a">Every export is stamped with workspace ID, requesting user, and timestamp in file metadata, and written to the audit log (<code>audit.annotation_export</code>). Files are served via expiring signed URLs (24h) and never stored beyond that window.
<br><a class="ref" href="#spec">&#8599; spec &sect;6 — Audit &amp; retention</a></div>
</details>
<!-- 4 · SPEC SUMMARY -->
<h3 class="sec" id="spec"><span class="n">04</span> Spec at a glance</h3>
<div class="tblwrap">
<table class="spec">
<thead><tr><th>Area</th><th>Decision</th><th>Ref</th></tr></thead>
<tbody>
<tr><td>Formats</td><td>SRT (timecoded cues), CSV (flat, incl. drawing bounding boxes), PDF (frame thumbnails + comments)</td><td class="specref">&sect;2</td></tr>
<tr><td>Entry point</td><td>Annotation panel &rarr; multi-select &rarr; <b>Export selected</b>; also &ldquo;Export all&rdquo; in session menu</td><td class="specref">&sect;3.1</td></tr>
<tr><td>Permissions</td><td>Requires <code>session:comment</code> or above; <code>internal</code> annotations excluded by default</td><td class="specref">&sect;4.2</td></tr>
<tr><td>Limits</td><td>500 annotations/batch, streamed; 25k hard cap per export with clear error state</td><td class="specref">&sect;5.3</td></tr>
<tr><td>Telemetry</td><td><code>export_started</code>, <code>export_completed</code> (format, count, duration), <code>export_failed</code></td><td class="specref">&sect;7</td></tr>
<tr><td>Feature flag</td><td><code>annotation_export</code> — off &rarr; 5% &rarr; 50% &rarr; 100% over two weeks</td><td class="specref">&sect;8</td></tr>
</tbody>
</table>
</div>
<!-- 5 · RISK + NEEDS -->
<h3 class="sec"><span class="n">05</span> Risk, rollback, and what I need from you</h3>
<div class="cardgrid">
<div class="risk">
<h4>Risk &amp; rollback</h4>
<ul>
<li><strong>Rollback is one toggle.</strong> Flip <code>annotation_export</code> off — no schema migrations shipped, nothing to unwind.</li>
<li><strong>Blast radius is bounded.</strong> Export runs in the shared worker pool with a 30s per-job timeout; a runaway job cannot starve review playback.</li>
<li><strong>Worst credible failure:</strong> orphaned export files if the signed-URL cleanup lags — they self-expire in 24h regardless.</li>
<li><strong>Known gap:</strong> drawing annotations flatten to bounding boxes in CSV (full vectors are PDF-only). Documented in-product; phase 2 item.</li>
</ul>
</div>
<div class="need">
<h4>What I need from you</h4>
<div class="approver">
<span class="avatar">PN</span>
<div class="info"><b>Priya Natarajan</b><span class="role">Eng lead, Playback &amp; Review</span>
<div class="asks">Sign off on reusing the render worker pool (spec &sect;5.1) — the only shared-infra dependency.</div></div>
</div>
<div class="approver">
<span class="avatar">ES</span>
<div class="info"><b>Elena Sorokin</b><span class="role">Security</span>
<div class="asks">Approve the visibility rules and audit-log shape (spec &sect;4.2, &sect;6).</div></div>
</div>
<div class="approver">
<span class="avatar">MW</span>
<div class="info"><b>Marcus Webb</b><span class="role">Design</span>
<div class="asks">Final pass on the export modal and error states — prototype above is current.</div></div>
</div>
<div class="approver">
<span class="avatar">DK</span>
<div class="info"><b>Dana Kim</b><span class="role">PM, Collaboration</span>
<div class="asks">Approve the flag ramp schedule (spec &sect;8) and own the changelog entry.</div></div>
</div>
</div>
</div>
<div class="bottomline">Everything above is built and tested behind the flag. Reply with a &#10003; on your piece, or grab 15 minutes on my calendar — <b>target: ramp starts Monday.</b></div>
</article>
<footer>Part of <a href="index.html">Know your unknowns</a> — companion examples to the blog post.</footer>
</div>
<script>
(function () {
// ---------- copy prompt ----------
var PROMPT = "Package the prototype, the spec, and the implementation notes into a single doc I can drop in Slack to get buy-in on shipping annotation export. Lead with the demo.";
var copyBtn = document.getElementById("copyBtn");
copyBtn.addEventListener("click", function () {
function done() {
copyBtn.textContent = "copied";
copyBtn.classList.add("done");
setTimeout(function () {
copyBtn.textContent = "copy prompt";
copyBtn.classList.remove("done");
}, 1600);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(PROMPT).then(done, fallback);
} else { fallback(); }
function fallback() {
var ta = document.createElement("textarea");
ta.value = PROMPT;
ta.style.position = "fixed"; ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); } catch (e) {}
document.body.removeChild(ta);
done();
}
});
// ---------- demo animation ----------
var anns = [document.getElementById("ann1"), document.getElementById("ann2"), document.getElementById("ann3")];
var pins = [document.getElementById("pin1"), document.getElementById("pin2"), document.getElementById("pin3")];
var exportBtn = document.getElementById("exportBtn");
var progress = document.getElementById("progress");
var progBar = document.getElementById("progBar");
var progStatus = document.getElementById("progStatus");
var toast = document.getElementById("toast");
var selCount = document.getElementById("selCount");
var timers = [];
function at(ms, fn) { timers.push(setTimeout(fn, ms)); }
function reset() {
timers.forEach(clearTimeout);
timers = [];
anns.forEach(function (a) { a.classList.remove("sel"); });
pins.forEach(function (p) { p.classList.remove("on"); });
exportBtn.classList.remove("armed", "pressed");
progress.classList.remove("show");
progBar.style.transition = "none";
progBar.style.width = "0%";
// force reflow so the next width change animates
void progBar.offsetWidth;
progBar.style.transition = "";
progStatus.textContent = "Collecting annotations…";
toast.classList.remove("show");
selCount.textContent = "0 selected";
}
function play() {
reset();
[0, 1, 2].forEach(function (i) {
at(500 + i * 450, function () {
anns[i].classList.add("sel");
pins[i].classList.add("on");
selCount.textContent = (i + 1) + " selected";
if (i === 2) exportBtn.classList.add("armed");
});
});
at(2100, function () { exportBtn.classList.add("pressed"); });
at(2300, function () {
exportBtn.classList.remove("pressed");
progress.classList.add("show");
});
at(2450, function () { progBar.style.width = "100%"; });
at(3100, function () { progStatus.textContent = "Rendering SRT cues…"; });
at(3800, function () { progStatus.textContent = "Signing download URL…"; });
at(4300, function () {
progress.classList.remove("show");
toast.classList.add("show");
});
at(7000, play); // loop
}
document.getElementById("replayBtn").addEventListener("click", play);
play();
})();
</script>
</body>
</html>