[object Object]

← back to Draft Viewer

Rebuild the board around Steve's 2026-09-10 decisions — send 7, keep 13, 7 inert

f7ed61fb22374344929f74698b3f3df3ac22fb96 · 2026-09-10 08:59:09 -0700 · Steve Abrams

The old board was split ready/delete, which no longer matches reality: Steve
ruled send-both-fuse-drafts + send-all-5-vendor-asks, 'put in drafts' for the 13
customer replies, and that HE clicks Send in Gmail. So the board is now grouped
by action and it neither sends nor deletes anything.

- build-board.mjs derives every age from Gmail's own internalDate in the
  snapshot rather than a hand-typed number, so the fuse cannot silently drift.
  It reads 3d (bmwallpaper) and 4d (Kravet) until permanent deletion.
- The two fuse drafts were never on the old board at all; they are now the
  first thing on the page.
- Cards carry the created date+time chip and a snapshot marker, so it is
  visible at a glance that the text survives even if Gmail loses the draft.

Verified: 401 gate healthy, 200 authed, 7/13/7 cards, 27 date chips, rendered
in a real browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Files touched

Diff

commit f7ed61fb22374344929f74698b3f3df3ac22fb96
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 08:59:09 2026 -0700

    Rebuild the board around Steve's 2026-09-10 decisions — send 7, keep 13, 7 inert
    
    The old board was split ready/delete, which no longer matches reality: Steve
    ruled send-both-fuse-drafts + send-all-5-vendor-asks, 'put in drafts' for the 13
    customer replies, and that HE clicks Send in Gmail. So the board is now grouped
    by action and it neither sends nor deletes anything.
    
    - build-board.mjs derives every age from Gmail's own internalDate in the
      snapshot rather than a hand-typed number, so the fuse cannot silently drift.
      It reads 3d (bmwallpaper) and 4d (Kravet) until permanent deletion.
    - The two fuse drafts were never on the old board at all; they are now the
      first thing on the page.
    - Cards carry the created date+time chip and a snapshot marker, so it is
      visible at a glance that the text survives even if Gmail loses the draft.
    
    Verified: 401 gate healthy, 200 authed, 7/13/7 cards, 27 date chips, rendered
    in a real browser.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
 build-board.mjs |  78 ++++++++++
 data/board.json | 449 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js       | 160 +++++++++++++-------
 3 files changed, 630 insertions(+), 57 deletions(-)

diff --git a/build-board.mjs b/build-board.mjs
new file mode 100644
index 0000000..35bf5dd
--- /dev/null
+++ b/build-board.mjs
@@ -0,0 +1,78 @@
+#!/usr/bin/env node
+// build-board.mjs — TK-11231
+// Rebuilds data/board.json from the on-disk snapshots, grouped by the ACTION
+// Steve decided for each class on 2026-09-10, not by the old ready/delete split.
+//
+// Ages come from Gmail's own internalDate captured in the snapshot, never from a
+// hand-typed number — an age that drifts is how a fuse gets missed.
+
+import fs from 'node:fs';
+import path from 'node:path';
+
+const HERE = path.dirname(new URL(import.meta.url).pathname);
+const BODIES = path.join(HERE, 'data', 'bodies');
+
+// info@ drafts older than 30 days are PERMANENTLY deleted (no Trash) by
+// com.steve.george-drain-old-drafts, daily 04:15. steve-office is never touched.
+const PURGE_DAYS = 30;
+
+const CUSTOMER = new Set(['19ff677a456e1e61','19ff67c7ca93d3ee','19ff677c9748dd70','19ff677d1895b6f4',
+  '19ff67c4e29d46c2','19ff67c3b9d347b7','19ff67c334844485','19ff67c288b79265','19ff67c1ef9c846b',
+  '19ff67c543906d8f','19ff67c5f7a58198','19ff67c6cc0dc4e4','19ff67c7686cc8dc']);
+const VENDOR_SEND = new Set(['1a06d5e2baaf3149','1a06d5e530b2ec40','1a06d55e4f511b8e','1a06d2ea705dddd3','1a06d1ef99f846d1']);
+const FUSE_SEND   = new Set(['1a006aa890e66af3','1a005fc3ff6c56ff']);
+
+const strip = (h) => String(h || '').replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<[^>]+>/g, ' ')
+  .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/\s+/g, ' ').trim();
+
+const rows = [];
+for (const f of fs.readdirSync(BODIES).filter((x) => x.endsWith('.json'))) {
+  const { board, message } = JSON.parse(fs.readFileSync(path.join(BODIES, f), 'utf8'));
+  const created = message.internalDate ? new Date(Number(message.internalDate)) : null;
+  const ageDays = created ? (Date.now() - created.getTime()) / 864e5 : null;
+  // Only info@ has a fuse; a steve-office draft can sit forever.
+  const daysLeft = board.account === 'info' && ageDays != null ? Math.floor(PURGE_DAYS - ageDays) : null;
+
+  const bucket = FUSE_SEND.has(board.id) ? 'send_now'
+    : VENDOR_SEND.has(board.id) ? 'send_now'
+    : CUSTOMER.has(board.id) ? 'keep'
+    : 'superseded';
+
+  rows.push({
+    id: board.id, account: board.account, bucket,
+    fuse: FUSE_SEND.has(board.id),
+    vendor: board.vendor || null,
+    subject: message.subject || '(no subject)',
+    to: message.to || board.to || null,
+    gap: board.gap || null,
+    why: board.why || null,
+    created: created ? created.toISOString() : null,
+    age_days: ageDays == null ? null : Math.round(ageDays * 10) / 10,
+    days_left: daysLeft,
+    preview: strip(message.body).slice(0, 260),
+    snapshot_file: `data/bodies/${f}`,
+  });
+}
+
+const order = { send_now: 0, keep: 1, superseded: 2 };
+rows.sort((a, b) => (order[a.bucket] - order[b.bucket])
+  || (a.fuse === b.fuse ? 0 : a.fuse ? -1 : 1)
+  || ((a.days_left ?? 999) - (b.days_left ?? 999))
+  || (b.age_days - a.age_days));
+
+fs.writeFileSync(path.join(HERE, 'data', 'board.json'), JSON.stringify({
+  built_at: new Date().toISOString(),
+  decided_at: '2026-09-10',
+  decisions: {
+    send_now: 'Steve 2026-09-10: send both fuse drafts as-is + all 5 vendor asks. He clicks Send in Gmail himself — nothing is auto-fired.',
+    keep: 'Steve 2026-09-10: "put in drafts" — the 13 stale customer replies are NOT deleted and NOT auto-sent. They stay as drafts; bodies are unedited.',
+    superseded: 'No decision taken. Duplicates and wrong-scope asks already replaced by fresh drafts. Inert on steve-office, which the drainer never touches.',
+  },
+  purge_days: PURGE_DAYS,
+  counts: rows.reduce((a, r) => ((a[r.bucket] = (a[r.bucket] || 0) + 1), a), {}),
+  rows,
+}, null, 2));
+
+console.log('board.json built:', rows.length, 'rows',
+  JSON.stringify(rows.reduce((a, r) => ((a[r.bucket] = (a[r.bucket] || 0) + 1), a), {})));
+for (const r of rows.filter((x) => x.fuse)) console.log(`  FUSE ${r.days_left}d left — ${r.to}`);
diff --git a/data/board.json b/data/board.json
new file mode 100644
index 0000000..a157c9b
--- /dev/null
+++ b/data/board.json
@@ -0,0 +1,449 @@
+{
+  "built_at": "2026-09-10T15:56:36.562Z",
+  "decided_at": "2026-09-10",
+  "decisions": {
+    "send_now": "Steve 2026-09-10: send both fuse drafts as-is + all 5 vendor asks. He clicks Send in Gmail himself — nothing is auto-fired.",
+    "keep": "Steve 2026-09-10: \"put in drafts\" — the 13 stale customer replies are NOT deleted and NOT auto-sent. They stay as drafts; bodies are unedited.",
+    "superseded": "No decision taken. Duplicates and wrong-scope asks already replaced by fresh drafts. Inert on steve-office, which the drainer never touches."
+  },
+  "purge_days": 30,
+  "counts": {
+    "send_now": 7,
+    "keep": 13,
+    "superseded": 7
+  },
+  "rows": [
+    {
+      "id": "1a005fc3ff6c56ff",
+      "account": "info",
+      "bucket": "send_now",
+      "fuse": true,
+      "vendor": null,
+      "subject": "Sample Follow-Up — Outstanding Memos (Acct On File) — Designer Wallcoverings",
+      "to": "sales@bmwallpaper.com",
+      "gap": null,
+      "why": "Agent-composed sample follow-up, outstanding memos, account on file.",
+      "created": "2026-08-15T15:13:32.000Z",
+      "age_days": 26,
+      "days_left": 3,
+      "preview": "From job: node · Aug 15, 2026, 8:13 AM We are following up because we have not received the following samples from 10 days ago. Hopefully we removed any disco items internally from this list before asking again. Our account number is On File We ordered 1 memo ",
+      "snapshot_file": "data/bodies/info__1a005fc3ff6c56ff.json"
+    },
+    {
+      "id": "1a006aa890e66af3",
+      "account": "info",
+      "bucket": "send_now",
+      "fuse": true,
+      "vendor": null,
+      "subject": "Re: Sample Follow-Up — Outstanding Memos (Acct 10087117) — Designer Wallcoverings",
+      "to": "matt.schoffman@kravet.com",
+      "gap": null,
+      "why": "A named Kravet rep already sent us tracking; our confirmation reply was never sent.",
+      "created": "2026-08-15T18:23:54.000Z",
+      "age_days": 25.9,
+      "days_left": 4,
+      "preview": "From job: node · Aug 15, 2026, 11:23 AM Hi Matt, Thank you for the tracking! Quick confirmation on the four that show delivered — 2019101.34.0, 2019101.313.0, F1562/04.CAC.0 and F1562/02.CAC.0: were those shipped to our Sherman Oaks office, or drop-shipped dir",
+      "snapshot_file": "data/bodies/info__1a006aa890e66af3.json"
+    },
+    {
+      "id": "1a06d1ef99f846d1",
+      "account": "info",
+      "bucket": "send_now",
+      "fuse": false,
+      "vendor": "Newmor / LBI Boyd",
+      "subject": "Newmor pricing — 3 quick questions",
+      "to": "JCarol@lbiboyd.com",
+      "gap": "1,294 rows · 0 priced · 305 products dark",
+      "why": null,
+      "created": "2026-09-04T15:52:21.000Z",
+      "age_days": 6,
+      "days_left": 23,
+      "preview": "From job: node · Sep 4, 2026, 8:52 AM Hi Jill, Three quick ones on Newmor — we're getting the full range listed on our site and pricing is the last piece. Short answers are fine. Net vs list. On the 24th you gave me KOTA Silk / PIAZZA at $30.95 per yard list, ",
+      "snapshot_file": "data/bodies/info__1a06d1ef99f846d1.json"
+    },
+    {
+      "id": "1a06d2ea705dddd3",
+      "account": "steve-office",
+      "bucket": "send_now",
+      "fuse": false,
+      "vendor": "Fentucci Naturals",
+      "subject": "Net cost list request — Fentucci Naturals grasscloth (148 SKUs)",
+      "to": "tokiwausa@tokiwa.net",
+      "gap": "148 rows · no cost column",
+      "why": null,
+      "created": "2026-09-04T16:09:29.000Z",
+      "age_days": 6,
+      "days_left": null,
+      "preview": "From job: node · Sep 4, 2026, 9:09 AM Hello, Steve Abrams at Designer Wallcoverings . We carry the full Fentucci Naturals grasscloth line and I'm updating our pricing so it reflects your current numbers. Could you send your current net/wholesale cost list for ",
+      "snapshot_file": "data/bodies/steve-office__1a06d2ea705dddd3.json"
+    },
+    {
+      "id": "1a06d55e4f511b8e",
+      "account": "steve-office",
+      "bucket": "send_now",
+      "fuse": false,
+      "vendor": "Reid Witlin",
+      "subject": "Net cost list request — full Reid Witlin line (Designer Wallcoverings, trade)",
+      "to": "samples@rwltd.com",
+      "gap": "1,470 rows · 0 cost (1,070 price only)",
+      "why": null,
+      "created": "2026-09-04T16:52:20.000Z",
+      "age_days": 6,
+      "days_left": null,
+      "preview": "From job: node · Sep 4, 2026, 9:52 AM Hello, Steve Abrams at Designer Wallcoverings — a to-the-trade wallcovering and fabric retailer. We carry the Reid Witlin line and I'm updating our pricing so it reflects your current numbers. If this isn't the right desk,",
+      "snapshot_file": "data/bodies/steve-office__1a06d55e4f511b8e.json"
+    },
+    {
+      "id": "1a06d5e2baaf3149",
+      "account": "steve-office",
+      "bucket": "send_now",
+      "fuse": false,
+      "vendor": "Command Wallcovering",
+      "subject": "Net cost list request — full Command Wallcovering line (Designer Wallcoverings, trade)",
+      "to": "Customerservice@nationalsolutions.com",
+      "gap": "2,120 rows · 0 trade · 0 retail",
+      "why": null,
+      "created": "2026-09-04T17:01:22.000Z",
+      "age_days": 6,
+      "days_left": null,
+      "preview": "From job: node · Sep 4, 2026, 10:01 AM Hello, Steve Abrams at Designer Wallcoverings & Fabrics — a design-trade showroom and online retailer serving interior designers, architects, and commercial/hospitality clients nationwide. I understand National Solutions ",
+      "snapshot_file": "data/bodies/steve-office__1a06d5e2baaf3149.json"
+    },
+    {
+      "id": "1a06d5e530b2ec40",
+      "account": "steve-office",
+      "bucket": "send_now",
+      "fuse": false,
+      "vendor": "Vahallan",
+      "subject": "Net cost list request — full Vahallan line (Designer Wallcoverings, trade)",
+      "to": "info@vahallan.com",
+      "gap": "390 rows · no cost column",
+      "why": null,
+      "created": "2026-09-04T17:01:33.000Z",
+      "age_days": 6,
+      "days_left": null,
+      "preview": "From job: node · Sep 4, 2026, 10:01 AM Hello Vahallan team, Steve Abrams at Designer Wallcoverings — a to-the-trade wallcovering and fabric retailer. We've long admired your hand-painted line and want to carry the full collection properly for our designer clie",
+      "snapshot_file": "data/bodies/steve-office__1a06d5e530b2ec40.json"
+    },
+    {
+      "id": "19ff677a456e1e61",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Your Lee Jofa Levens Velvet quote (DWKK-134995)",
+      "to": "info@willemhendrik.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale — your call: delete",
+      "created": "2026-08-12T14:54:23.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:54 AM Hi Willem, Happy to help with the ~16 yards of Lee Jofa Levens Velvet in Navy (DWKK-134995). List is $476.85/yd, and since this is for a client proposal I can set you up with trade pricing — no account needed to order. Wa",
+      "snapshot_file": "data/bodies/steve-office__19ff677a456e1e61.json"
+    },
+    {
+      "id": "19ff677c9748dd70",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Yes — you can buy the Merlot pillows (not just samples)",
+      "to": "jane@panattoni.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:54:33.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:54 AM Hi Jane, You can absolutely purchase the Schumacher Performance Silk Velvet pillows in Merlot — the site just defaults to showing the sample. They're $495.93 each, so three would be $1,487.79 plus shipping. Reply with you",
+      "snapshot_file": "data/bodies/steve-office__19ff677c9748dd70.json"
+    },
+    {
+      "id": "19ff677d1895b6f4",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Ready to place your Wolf Gordon Forge order",
+      "to": "laura@trinitycgi.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:54:35.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:54 AM Hi Laura, Following up on your note that you'd like to order the Wolf Gordon Forge (Powder). Just tell me the quantity/rooms and I'll get it set up and moving for you. Best, Steve Designer Wallcoverings",
+      "snapshot_file": "data/bodies/steve-office__19ff677d1895b6f4.json"
+    },
+    {
+      "id": "19ff67c1ef9c846b",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Expedited samples — happy to rush them",
+      "to": "shani@elizabethmartell.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:17.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Shani, Yes — we can expedite your samples. Just send me the patterns (or your cart) and your shipping address, and I'll rush them out. Best, Steve Designer Wallcoverings",
+      "snapshot_file": "data/bodies/steve-office__19ff67c1ef9c846b.json"
+    },
+    {
+      "id": "19ff67c288b79265",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Roll count for your 368 sq ft bedroom",
+      "to": "meankittydesign@gmail.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:20.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Rita, For a 368 sq ft bedroom in the Hot Diggity Dog tile, I'll calculate the exact roll count (accounting for pattern repeat + trim) and send it over with pricing so you can order with confidence. Want me to run that ",
+      "snapshot_file": "data/bodies/steve-office__19ff67c288b79265.json"
+    },
+    {
+      "id": "19ff67c334844485",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Coverage + pricing for the Fleur de Lis",
+      "to": "leslieofthelake@gmail.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:22.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Leslie, For 120 sq ft of the Maya Romanoff Fleur de Lis (Beige & Cream), I'll confirm exactly how many rolls you need along with the per-roll coverage and price, then send a quote. Best, Steve Designer Wallcoverings",
+      "snapshot_file": "data/bodies/steve-office__19ff67c334844485.json"
+    },
+    {
+      "id": "19ff67c3b9d347b7",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Your Lee Jofa Lagoon sample",
+      "to": "ncsfoster@yahoo.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:24.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Nancy, Great eye — it was gorgeous in Southern Living! I'll get a sample of the Arley Paper Lagoon Multi by Lee Jofa out to you; just confirm your mailing address. Best, Steve Designer Wallcoverings",
+      "snapshot_file": "data/bodies/steve-office__19ff67c3b9d347b7.json"
+    },
+    {
+      "id": "19ff67c4e29d46c2",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Roberto Cavalli RC21003 pricing",
+      "to": "davidik@yahoo.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:29.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi David, Happy to price the Roberto Cavalli RC21003 for you. I'll pull the current per-roll pricing — how many rolls, and what's the space? Best, Steve Designer Wallcoverings",
+      "snapshot_file": "data/bodies/steve-office__19ff67c4e29d46c2.json"
+    },
+    {
+      "id": "19ff67c543906d8f",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Fidelity Flora NT65 / Glamour CM178 for your project",
+      "to": "andrew@berrybd.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:31.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Andrew, I can source both Fidelity options — Flora NT65 (Rose) and Glamour CM178. I'll confirm availability, lead time, and trade pricing. Shall I quote both, or are you leaning one way? Best, Steve Designer Wallcoveri",
+      "snapshot_file": "data/bodies/steve-office__19ff67c543906d8f.json"
+    },
+    {
+      "id": "19ff67c5f7a58198",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Glass beaded wallcovering for your art project",
+      "to": "jeanettegriffith927@gmail.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:33.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Jeanette, Love that you're using it for art. The clear glass beaded wallcovering is normally sold by the roll, but tell me how much you need and I'll see what we can do on a partial / by-the-yard basis for your project",
+      "snapshot_file": "data/bodies/steve-office__19ff67c5f7a58198.json"
+    },
+    {
+      "id": "19ff67c6cc0dc4e4",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Clearing up the Brand McKenzie Happy Hour colorway",
+      "to": "bgrennon@tmpartners.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:36.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Bonnee, Thanks for flagging the color confusion on the Brand McKenzie Happy Hour. I'll confirm exactly which colorway that listing and image show (Day vs. the dark green) so you order the right one. Which look are you ",
+      "snapshot_file": "data/bodies/steve-office__19ff67c6cc0dc4e4.json"
+    },
+    {
+      "id": "19ff67c7686cc8dc",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Let's find that bamboo mural from our email",
+      "to": "mrb33981@outlook.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:39.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Michael, I can help you track down that bamboo/tree mural you saw in our email. Just reply with the photo (or the date of the email) and I'll identify the exact pattern for your bedroom. Best, Steve Designer Wallcoveri",
+      "snapshot_file": "data/bodies/steve-office__19ff67c7686cc8dc.json"
+    },
+    {
+      "id": "19ff67c7ca93d3ee",
+      "account": "steve-office",
+      "bucket": "keep",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Stock check on your Graham & Brown Weave Emerald",
+      "to": "foxtaledesign@gmail.com",
+      "gap": null,
+      "why": "customer reply, 23 days stale",
+      "created": "2026-08-12T14:59:41.000Z",
+      "age_days": 29,
+      "days_left": null,
+      "preview": "From job: node · Aug 12, 2026, 7:59 AM Hi Dena, Checking availability on the Graham & Brown Weave Emerald (DWGB-800139) in your cart now — I'll confirm stock and lead time and help you get checked out. Best, Steve Designer Wallcoverings",
+      "snapshot_file": "data/bodies/steve-office__19ff67c7ca93d3ee.json"
+    },
+    {
+      "id": "1a015db4fe26802c",
+      "account": "info",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Request: current 2026 wholesale cost list — Newmor (+ full LBI Boyd line card)",
+      "to": "jill@lbiboyd.com",
+      "gap": null,
+      "why": "superseded — bundled 5 lines into one cold ask",
+      "created": "2026-08-18T17:11:29.000Z",
+      "age_days": 22.9,
+      "days_left": 7,
+      "preview": "From job: node · Aug 18, 2026, 10:11 AM Hi Jill, Hope you're well. We're refreshing the Newmor program on our site and want to make sure our pricing reflects your current numbers. Could you send your most recent 2026 wholesale cost list for Newmor ? A per-SKU ",
+      "snapshot_file": "data/bodies/info__1a015db4fe26802c.json"
+    },
+    {
+      "id": "1a059922f619b980",
+      "account": "info",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Width spec request — 2 discontinued Maya Romanoff patterns",
+      "to": null,
+      "gap": null,
+      "why": "DUPLICATE of the steve-office copy, 2 min apart",
+      "created": "2026-08-31T20:45:47.000Z",
+      "age_days": 9.8,
+      "days_left": 20,
+      "preview": "From job: node · Aug 31, 2026, 1:45 PM Hi, Quick spec question on two older Maya Romanoff patterns we still carry — both look to be discontinued on your site, so I can't pull the roll width from the product pages: 1. Cozy Bed Fellow — Honey (item MR-CC-1506-H)",
+      "snapshot_file": "data/bodies/info__1a059922f619b980.json"
+    },
+    {
+      "id": "19f3d1a86a49b8c6",
+      "account": "steve-office",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Pricing request — Dr. Feelgood Henna (dr-feelgood-henna)",
+      "to": "samples@rwltd.com",
+      "gap": null,
+      "why": "WRONG SCOPE — asks 1 SKU, catalog needs 1,470",
+      "created": "2026-07-07T15:02:56.000Z",
+      "age_days": 65,
+      "days_left": null,
+      "preview": "From job: node · Jul 7, 2026, 8:02 AM Hello Reid Witlin team, Could you please confirm our current net / wholesale cost (and any applicable discount) on the following item? Pattern: Dr. Feelgood Henna Item / SKU: dr-feelgood-henna This is for Designer Wallcove",
+      "snapshot_file": "data/bodies/steve-office__19f3d1a86a49b8c6.json"
+    },
+    {
+      "id": "19f3e1c8c8a01b4d",
+      "account": "steve-office",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Request: Full Command Wallcovering price list — Designer Wallcoverings & Fabrics (trade account)",
+      "to": "Customerservice@nationalsolutions.com",
+      "gap": null,
+      "why": "DUPLICATE — written 3h after the one above, same day",
+      "created": "2026-07-07T19:44:46.000Z",
+      "age_days": 64.8,
+      "days_left": null,
+      "preview": "From job: node · Jul 7, 2026, 12:44 PM Hello, My name is Steve Abrams, with Designer Wallcoverings & Fabrics — a design-trade showroom and online retailer serving interior designers, architects, and commercial/hospitality clients nationwide. We actively presen",
+      "snapshot_file": "data/bodies/steve-office__19f3e1c8c8a01b4d.json"
+    },
+    {
+      "id": "19f3e20f58596b3e",
+      "account": "steve-office",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Command Wallcovering — full price list request (Designer Wallcoverings & Fabrics, trade)",
+      "to": "Customerservice@nationalsolutions.com",
+      "gap": null,
+      "why": "superseded by today's fresh draft",
+      "created": "2026-07-07T19:49:35.000Z",
+      "age_days": 64.8,
+      "days_left": null,
+      "preview": "From job: node · Jul 7, 2026, 12:49 PM Hello, My name is Steve Abrams, with Designer Wallcoverings & Fabrics — a design-trade showroom and online retailer serving interior designers, architects, and commercial/hospitality clients nationwide. I understand Natio",
+      "snapshot_file": "data/bodies/steve-office__19f3e20f58596b3e.json"
+    },
+    {
+      "id": "19f906c4a8f0e8fe",
+      "account": "steve-office",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Trade / wholesale pricing request — Designer Wallcoverings",
+      "to": "info@vahallan.com",
+      "gap": null,
+      "why": "superseded by today's fresh draft",
+      "created": "2026-07-23T19:20:43.000Z",
+      "age_days": 48.9,
+      "days_left": null,
+      "preview": "From job: node · Jul 23, 2026, 12:20 PM Hello Vahallan team, I'm Steve Abrams with Designer Wallcoverings , a to-the-trade wallcovering and fabric retailer. We've been admirers of your hand-painted line and would love to carry the full Vahallan collection for ",
+      "snapshot_file": "data/bodies/steve-office__19f906c4a8f0e8fe.json"
+    },
+    {
+      "id": "1a059915205816a2",
+      "account": "steve-office",
+      "bucket": "superseded",
+      "fuse": false,
+      "vendor": null,
+      "subject": "Net Cost Request — Fentucci Naturals Line (118 SKUs)",
+      "to": "tokiwausa@tokiwa.net",
+      "gap": null,
+      "why": "BROKEN — promises an attachment that isn't there; count wrong (118 vs 148)",
+      "created": "2026-08-31T20:44:51.000Z",
+      "age_days": 9.8,
+      "days_left": null,
+      "preview": "From job: curl/8.7.1 · Aug 31, 2026, 1:44 PM Hi, Hope you are doing well. We carry the full Fentucci Naturals grasscloth line on Designer Wallcoverings and are in the process of updating our pricing system to properly reflect per-yard net costs. I have attache",
+      "snapshot_file": "data/bodies/steve-office__1a059915205816a2.json"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/server.js b/server.js
index 99822e4..d7ee40b 100644
--- a/server.js
+++ b/server.js
@@ -1,63 +1,109 @@
 const http=require('http'),fs=require('fs'),path=require('path');
 const USER='admin',PASS='DW2024!';
-const DATA=path.join(__dirname,'data','drafts.json');
-const gm=(acct,id)=>`https://mail.google.com/mail/u/${acct==='info'?'1':'0'}/#drafts/${id}`;
+const DATA=path.join(__dirname,'data','board.json');
+
+// Gmail deep link. Steve decided (2026-09-10) that HE clicks Send in Gmail —
+// this board never sends, never deletes, it only makes the right draft findable.
+const gm=(a,id)=>`https://mail.google.com/mail/u/${a==='info'?'1':'0'}/#drafts/${id}`;
 const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
+const when=iso=>iso?new Date(iso).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):'—';
+
+const BUCKET={
+  send_now:{t:'SEND THESE',k:'go',n:'Open each and click Send. Nothing here is fired for you.'},
+  keep:{t:'KEEP AS DRAFTS',k:'keep',n:'Your call: “put in drafts.” Not deleted, not sent, bodies unedited.'},
+  superseded:{t:'SUPERSEDED — NO ACTION',k:'old',n:'Duplicates and wrong-scope asks already replaced. Inert on steve-office.'}
+};
+
+function card(x){
+  // days_left only exists for info@, the one account the 04:15 drainer purges.
+  const fuse = x.days_left!=null && x.days_left<=7
+    ? `<span class="fuse${x.days_left<=4?' hot':''}">${x.days_left}d until permanent delete</span>`:'';
+  return `<article class="c ${x.bucket}${x.fuse?' isfuse':''}">
+  <header><span class="v">${esc(x.vendor||(x.to||'').split('@')[0]||'—')}</span>
+    <span class="acct">${esc(x.account)}</span>${fuse}</header>
+  <p class="s">${esc(x.subject)}</p>
+  ${x.to?`<p class="to">→ ${esc(x.to)}</p>`:''}
+  ${x.gap?`<p class="gap">${esc(x.gap)}</p>`:''}
+  ${x.why?`<p class="why">${esc(x.why)}</p>`:''}
+  <p class="pv">${esc(x.preview)}…</p>
+  <footer>
+    <time title="${esc(x.created||'')}">🕓 ${esc(when(x.created))} · ${esc(x.age_days)}d old</time>
+    <a href="${gm(x.account,x.id)}" target="_blank" rel="noopener noreferrer">open in gmail →</a>
+  </footer>
+  <code title="snapshot on disk: ${esc(x.snapshot_file)}">💾 ${esc(x.id)}</code></article>`;
+}
+
 function page(d){
-  // x.live is set by refresh.mjs. `false` means the draft no longer exists in Gmail — sent, or
-  // PERMANENTLY deleted by com.steve.george-drain-old-drafts (info@ >30d, no Trash). A board that
-  // renders a destroyed draft as if it were still actionable is worse than no board.
-  const card=(x,kind)=>`<article class="c ${kind}${x.customer?' cust':''}${x.live===false?' gone':''}">
-    <header><span class="v">${esc(x.vendor||x.subject.split('—')[0])}</span>
-    ${x.live===false?'<span class="gonebadge" title="No longer in Gmail — sent or permanently deleted">GONE</span>':''}
-    <span class="acct">${esc(x.account)}</span></header>
-    <p class="s">${esc(x.subject)}</p>
-    ${x.to?`<p class="to">→ ${esc(x.to)}</p>`:''}
-    ${x.gap?`<p class="gap">${esc(x.gap)}</p>`:''}
-    ${x.why?`<p class="why">${esc(x.why)}</p>`:''}
-    <footer><time>🕓 ${esc(x.created?new Date(x.created).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):x.age)}</time>
-    <a href="${gm(x.account,x.id)}" target="_blank" rel="noopener noreferrer">open in gmail →</a></footer>
-    <code>${esc(x.id)}</code></article>`;
+  const g=b=>d.rows.filter(r=>r.bucket===b);
+  const sec=b=>{const rows=g(b),m=BUCKET[b];return !rows.length?'':`
+  <section><h2 class="${m.k}">${m.t} <b>${rows.length}</b></h2>
+  <p class="note">${esc(m.n)}</p><p class="note dim">${esc(d.decisions[b])}</p>
+  <div class="grid">${rows.map(card).join('')}</div></section>`;};
+  const hot=g('send_now').filter(r=>r.days_left!=null&&r.days_left<=7)
+    .sort((a,b)=>a.days_left-b.days_left);
   return `<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1">
-<title>Drafts — ${d.ready.length} ready · ${d.delete.length} to delete</title><style>
-:root{--bg:#0e0e10;--fg:#e8e6e3;--dim:#8b8781;--line:#26262a;--go:#3fb950;--kill:#f85149;--cust:#d29922}
-*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,-apple-system,system-ui,sans-serif}
-h1{font-size:15px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);font-weight:600;margin:0}
-header.top{padding:20px 24px;border-bottom:1px solid var(--line);display:flex;gap:20px;align-items:baseline;flex-wrap:wrap;position:sticky;top:0;background:var(--bg);z-index:5}
-.k{font-size:12px;color:var(--dim)}.k b{color:var(--fg);font-size:16px}
-section{padding:20px 24px}h2{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);margin:0 0 14px}
-.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(var(--w,320px),1fr))}
-.c{border:1px solid var(--line);border-radius:8px;padding:13px 15px;background:#141416;border-left-width:3px}
-.c.gone{opacity:.42;filter:grayscale(.7)}.c.gone .s{text-decoration:line-through}.gonebadge{font:700 10px/1.6 ui-monospace,Menlo,monospace;letter-spacing:.12em;background:#4a4a4a;color:#eee;padding:1px 6px;border-radius:2px;margin-left:6px}.c.ready{border-left-color:var(--go)}.c.delete{border-left-color:var(--kill)}.c.cust{border-left-color:var(--cust)}
-.c header{display:flex;justify-content:space-between;gap:8px;align-items:baseline;margin-bottom:6px}
-.v{font-weight:650}.acct{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim);border:1px solid var(--line);border-radius:99px;padding:1px 7px}
-.s{margin:0 0 6px;font-size:13px}.to{margin:0 0 4px;font-size:12px;color:var(--dim);word-break:break-all}
-.gap{margin:0;font-size:12px;color:var(--go)}.why{margin:0;font-size:12px;color:var(--kill)}
-.c.cust .why{color:var(--cust)}
-.c footer{display:flex;justify-content:space-between;gap:10px;margin-top:9px;font-size:11px;align-items:center}
-time{color:var(--dim)}a{color:#58a6ff}code{display:block;margin-top:6px;font-size:10px;color:#4a4a50}
-.controls{margin-left:auto;display:flex;gap:14px;align-items:center;font-size:12px;color:var(--dim)}
-input[type=range]{width:130px}
-</style>
-<header class=top><h1>Unsent drafts</h1>
-<span class=k><b>${d.ready.length}</b> ready to send</span>
-<span class=k><b>${d.delete.length}</b> to delete</span>
-<span class=k style="color:var(--cust)"><b>${d.delete.filter(x=>x.customer).length}</b> stale customer replies</span>
-${d.verified_at?`<span class=k title="${esc(d.liveness_note||'')}">verified ${esc(new Date(d.verified_at).toLocaleString(undefined,{month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}))}</span>`:'<span class=k style="color:var(--kill)">NOT verified against live Gmail — run refresh.mjs</span>'}
-<span class=controls>density <input type=range min=240 max=520 value=320 id=dn></span></header>
-<section><h2 style="color:var(--go)">▸ Ready to send — ${d.ready.length}</h2>
-<div class="grid" id=g1>${d.ready.map(x=>card(x,'ready')).join('')}</div></section>
-<section><h2 style="color:var(--kill)">▸ Delete — ${d.delete.length}</h2>
-<div class="grid" id=g2>${d.delete.map(x=>card(x,'delete')).join('')}</div></section>
-<p style="padding:0 24px 30px;color:var(--dim);font-size:12px">${esc(d.note)} · snapshot ${esc(d.generated)}</p>
-<script>
-const dn=document.getElementById('dn');const set=v=>{document.querySelectorAll('.grid').forEach(g=>g.style.setProperty('--w',v+'px'));try{localStorage.setItem('dv-w',v)}catch(e){}};
-try{const s=localStorage.getItem('dv-w');if(s){dn.value=s;set(s)}}catch(e){}
-dn.addEventListener('input',e=>set(e.target.value));
-</script>`;}
+<title>TK-11231 drafts — ${g('send_now').length} to send</title><style>
+:root{--bg:#0e0e10;--fg:#e8e6e3;--dim:#8b8781;--line:#26262a;--go:#3fb950;--keep:#58a6ff;--old:#6e7681;--hot:#f85149;--warn:#d29922;--cols:3}
+*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.55 ui-sans-serif,-apple-system,system-ui,sans-serif}
+.wrap{max-width:1500px;margin:0 auto;padding:28px 22px 60px}
+h1{font-size:15px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);font-weight:600;margin:0 0 4px}
+.sub{color:var(--dim);font-size:12.5px;margin:0 0 18px}
+.alarm{border:1px solid var(--hot);background:#f851491a;border-radius:10px;padding:14px 16px;margin:0 0 24px}
+.alarm h3{margin:0 0 8px;font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--hot)}
+.alarm ul{margin:0;padding-left:18px}.alarm a{color:var(--fg)}
+.safe{border:1px solid var(--line);border-left:3px solid var(--go);border-radius:8px;padding:11px 14px;margin:0 0 24px;color:var(--dim);font-size:12.5px}
+.safe b{color:var(--go)}
+.bar{display:flex;gap:14px;align-items:center;margin:0 0 22px;color:var(--dim);font-size:12px}
+.bar input{accent-color:var(--keep)}
+h2{font-size:12.5px;letter-spacing:.14em;text-transform:uppercase;margin:30px 0 4px;font-weight:600}
+h2 b{font-weight:600;opacity:.6}
+h2.go{color:var(--go)}h2.keep{color:var(--keep)}h2.old{color:var(--old)}
+.note{color:var(--dim);font-size:12.5px;margin:0 0 4px}.note.dim{opacity:.62;margin-bottom:14px}
+.grid{display:grid;grid-template-columns:repeat(var(--cols),minmax(0,1fr));gap:12px}
+.c{border:1px solid var(--line);border-radius:9px;padding:13px 14px;background:#141417;display:flex;flex-direction:column;gap:6px;min-width:0}
+.c.send_now{border-left:3px solid var(--go)}.c.keep{border-left:3px solid var(--keep)}
+.c.superseded{border-left:3px solid var(--old);opacity:.62}
+.c.isfuse{border-color:var(--hot);background:#17100f}
+header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
+.v{font-weight:600;font-size:13px}
+.acct{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim);border:1px solid var(--line);border-radius:20px;padding:1px 7px}
+.fuse{font-size:10.5px;color:var(--warn);border:1px solid var(--warn);border-radius:20px;padding:1px 7px}
+.fuse.hot{color:var(--hot);border-color:var(--hot)}
+.s{margin:0;font-size:13px;line-height:1.4}
+.to{margin:0;font-size:12px;color:var(--keep);word-break:break-all}
+.gap,.why{margin:0;font-size:12px;color:var(--dim)}
+.pv{margin:4px 0 0;font-size:12px;color:var(--dim);opacity:.8;border-left:2px solid var(--line);padding-left:9px}
+footer{display:flex;justify-content:space-between;gap:10px;align-items:baseline;margin-top:auto;padding-top:8px;flex-wrap:wrap}
+time{font-size:11px;color:var(--dim)}
+footer a{font-size:12px;color:var(--go);text-decoration:none;white-space:nowrap}footer a:hover{text-decoration:underline}
+code{font-size:10px;color:var(--dim);opacity:.5}
+@media(max-width:900px){.grid{grid-template-columns:1fr}}
+</style><div class=wrap>
+<h1>TK-11231 · unsent draft graveyard</h1>
+<p class=sub>Decided ${esc(d.decided_at)} · built ${esc(when(d.built_at))} · this board never sends and never deletes.</p>
+
+${hot.length?`<div class=alarm><h3>⏳ Permanently deleted in ${hot[0].days_left} days — no Trash</h3>
+<ul>${hot.map(r=>`<li><b>${r.days_left}d</b> — <a href="${gm(r.account,r.id)}" target="_blank" rel="noopener noreferrer">${esc(r.to)}</a> — ${esc(r.subject)}</li>`).join('')}</ul></div>`:''}
+
+<div class=safe><b>The text is safe either way.</b> All ${d.rows.length} draft bodies are snapshotted to
+<code>data/bodies/</code>, so even if the 04:15 drainer destroys one, the words survive on disk.</div>
+
+<div class=bar><label>density <input type=range min=1 max=5 value=3 id=dz></label>
+<span>${d.counts.send_now||0} to send · ${d.counts.keep||0} kept · ${d.counts.superseded||0} superseded</span></div>
+${sec('send_now')}${sec('keep')}${sec('superseded')}
+</div><script>
+var dz=document.getElementById('dz'),K='tk11231.cols';
+function ap(v){document.documentElement.style.setProperty('--cols',v);dz.value=v;try{localStorage.setItem(K,v)}catch(e){}}
+try{var s=localStorage.getItem(K);if(s)ap(s)}catch(e){}
+dz.addEventListener('input',function(){ap(dz.value)});
+</script>`;
+}
+
 http.createServer((req,res)=>{
-  const a=(req.headers.authorization||'').split(' ')[1]||'';
-  if(Buffer.from(a,'base64').toString()!==USER+':'+PASS){res.writeHead(401,{'WWW-Authenticate':'Basic realm="drafts"'});return res.end('auth');}
-  let d;try{d=JSON.parse(fs.readFileSync(DATA,'utf8'))}catch(e){res.writeHead(500);return res.end('no snapshot: '+e.message)}
-  res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});res.end(page(d));
-}).listen(0,'127.0.0.1',function(){console.log('PORT='+this.address().port)});
+  const h=req.headers.authorization||'';
+  const ok=h.startsWith('Basic ')&&Buffer.from(h.slice(6),'base64').toString()===USER+':'+PASS;
+  if(!ok){res.writeHead(401,{'WWW-Authenticate':'Basic realm="drafts"'});return res.end('auth required');}
+  let d;try{d=JSON.parse(fs.readFileSync(DATA,'utf8'));}catch(e){res.writeHead(500);return res.end('board.json missing — run: node build-board.mjs');}
+  if(req.url.startsWith('/api/board')){res.writeHead(200,{'content-type':'application/json'});return res.end(JSON.stringify(d));}
+  res.writeHead(200,{'content-type':'text/html; charset=utf-8'});res.end(page(d));
+}).listen(0,'127.0.0.1',function(){console.log('PORT='+this.address().port);});

← b443b7c Snapshot every tracked draft body to disk — deletion is no l  ·  back to Draft Viewer  ·  TK-11231: archive + delete the 13 stale customer replies per 3fb2245 →