[object Object]

← back to Doing Viewer

doing-viewer: Kamatera mirror (snapshot publish + mirror server + 3min launchd sync)

72f6ce7fc38466f3f47026b2b0f94f4e4bd827e6 · 2026-08-20 12:25:20 -0700 · Steve

Files touched

Diff

commit 72f6ce7fc38466f3f47026b2b0f94f4e4bd827e6
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 20 12:25:20 2026 -0700

    doing-viewer: Kamatera mirror (snapshot publish + mirror server + 3min launchd sync)
---
 .deploy.conf        |  9 +++++++--
 data/snapshot.json  |  1 +
 public/index.html   | 14 ++++++++++++--
 publish-snapshot.js | 46 ++++++++++++++++++++++++++++++++++++++++++++
 server-mirror.js    | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 121 insertions(+), 4 deletions(-)

diff --git a/.deploy.conf b/.deploy.conf
index f52ce87..caae78f 100644
--- a/.deploy.conf
+++ b/.deploy.conf
@@ -1,2 +1,7 @@
-PROJECT_NAME=doing-viewer
-HEALTH_URL=http://127.0.0.1:9790/api/doing
+PROJECT_NAME=doing-mirror
+DEPLOY_HOST=45.61.58.125
+DEPLOY_PATH=/root/Projects/doing-viewer
+HEALTH_URL=http://localhost:9802/api/healthz
+REQUIRED_ENVS=""
+INSTALL_CMD="true"
+BUILD_CMD=""
diff --git a/data/snapshot.json b/data/snapshot.json
new file mode 100644
index 0000000..85cfc80
--- /dev/null
+++ b/data/snapshot.json
@@ -0,0 +1 @@
+{"syncedAt":"2026-08-20T19:25:10.226Z","source":"mac2","count":29,"items":[{"id":"TK-10513-fabricut-scraper-full-site-cloudflare-ma","title":"Fabricut scraper: full-site Cloudflare managed challenge blocks catalog nav (46 products, LOW value / HIGH effort - needs stealth browser or 2captcha cf_clearance; recommend defer)","project":"Designer-Wallcoverings","agent":"fabricut-exec","doingText":"Steve APPROVED launchd daily cadence (9:05am, every day). Memo→APPROVED. Steve runs the launchctl bootstrap in-session (! paste). Once armed, posts >=10 full-pattern Fabricut SKUs daily, G&YT-excluded. Disarm=launchctl bootout.","doingTs":"2026-08-20T19:24:13.450Z","ageH":0.01577,"eli12":"This task is trying to get product info from a website that's hiding it behind a tricky security system, like a locked door with a puzzle. Right now, it's set up to run every day, but it needs some special tools to get past the puzzle, and it's not very efficient."},{"id":"TK-10703-standardize-crcp-viewer-to-canonical-crc","title":"Standardize CRCP viewer to canonical CRCP UI across all pages (just-listed = reference)","project":"commercialrealestate","agent":"claude-crcp-popup","doingText":"@crcp-ui popup double-fire fix spec handed off (Steve DTD verdict A) — see docs/POPUP-COLLISION-FIX-for-crcp-ui.md. claude-crcp-popup standing down on index.html; you own the 2 edits (row-notes.js localStorage->server /api/agent-notes; remove my #pdOverlay block L~2495-2667). Stops us overwriting each other.","doingTs":"2026-08-20T19:17:17.104Z","ageH":0.13142166666666666,"eli12":"We’re making sure the CRCP viewer looks and works the same way on every page, like a consistent blueprint. Right now, we fixed a problem where a popup was appearing twice, and we’re cleaning up some old code to avoid future conflicts."},{"id":"TK-10579-rentv-bloom-linkedin-fan-out-batch","title":"RENTV Bloom LinkedIn fan-out batch","project":"rentv-sheet-enrich","agent":"ticket-triage","doingText":"STALE-TRIAGE: 5.2d idle. Real WIP (yoloforever cycle 2, LinkedIn(Contact) 514->517, 0 fabrications). Sibling of TK-10533. Needs resume. Left open.","doingTs":"2026-08-20T19:03:57.494Z","ageH":0.35353555555555555,"eli12":"This task is trying to help find new people on LinkedIn to connect with for a project. Right now, it’s been sitting still for a few days and needs someone to pick it back up and finish the work."},{"id":"TK-10533-enrich-rentv-cre-conference-spreadsheet","title":"Enrich RENTV CRE conference spreadsheet (17 tabs) from RE-build data + web research","project":"rentv","agent":"ticket-triage","doingText":"STALE-TRIAGE: 6.5d idle. Real WIP stalled at ~15% coverage (20/138 batches, ~590 contacts, ~92% Cody precision). Not done — needs resume. Left open.","doingTs":"2026-08-20T19:03:57.355Z","ageH":0.3535741666666667,"eli12":"This task is trying to fill in a big spreadsheet with useful information about a conference by combining data from another source and some online research. Right now, it's been sitting idle for a while and only got about a fifth of the way done — it needs to be picked back up to finish the job."},{"id":"TK-10168-fix-43k-product-image-url-health-backlog","title":"Fix ~43k product image-URL health backlog (17k dead Shopify imgs + 26k other-URL)","project":"dw_unified","agent":"ticket-triage","doingText":"STALE-TRIAGE: 8.2d idle. Progress logged is a sub-slice (DWAA-53: 48 broken orphans archived, Steve verdict A) — the headline 43k-image backlog is NOT shown done. Genuine partial WIP; needs resume. Left open.","doingTs":"2026-08-20T19:03:57.216Z","ageH":0.3536127777777778,"eli12":"This task is trying to fix a big list of broken image links on a website, so the pictures show up correctly again. Right now, some progress has been made, but most of the work is still waiting to be done — it’s like cleaning up a messy room, and only a few toys have been put away so far."},{"id":"TK-16","title":"Add more free west-coast property feeds (LA County + others) to deed/assessment layer","project":"usrealestate","agent":"ticket-triage","doingText":"STALE-TRIAGE: 9.6d idle. Last activity was code-hygiene cleanup of parcels.ts/html (lint, tsc clean) — NOT the feature (add more free west-coast feeds). Actual feed-add appears incomplete/undocumented here. Needs a resume-or-close decision.","doingTs":"2026-08-20T19:03:57.074Z","ageH":0.3536522222222222,"eli12":"This task is about adding more free property data sources from the West Coast, like LA County, to help make a map of property records more complete. Right now, the work on adding these data sources hasn’t moved forward in a while and needs someone to either pick it up again or decide to stop it."},{"id":"TK-10016-scope-make-individual-dw-tools-independe","title":"Scope: make individual DW tools independently public (deploy each to Kamatera)","project":"Designer-Wallcoverings","agent":"ticket-triage","doingText":"STALE-TRIAGE: 20+ days idle. Scope was DECIDED (Steve tiered-publish approval 7/31: Tier-1 publish, Tier-2 with-care, Tier-3 tailnet-only). No execution tracked here since. RECOMMEND Steve close this scope epic — per-tool deploys should be their own tickets. Left OPEN pending Steve's call (his scope item).","doingTs":"2026-08-20T19:03:56.932Z","ageH":0.3536916666666667,"eli12":"This task is about making each data tool public one by one on a special server called Kamatera. Right now, it's been stuck for over 20 days and hasn't moved forward — it needs to be closed and broken into smaller, clearer tasks for better progress."},{"id":"TK-10633-reconcile-the-hollywood-wallcoverings-hw","title":"Reconcile the Hollywood Wallcoverings HWC line (223 SKUs) — restore original codes + recover true mfr SKUs from LBI Boyd source","project":"dw-catalog","agent":"dwhd-repoint","doingText":"Option C EXECUTED + verified: 124/124 applied, 62 products DWHD->HWC live, mfr_sku untouched, canary -124 fabricated_variant/-62 products, rollback+ledger recorded, memo->_done. 584 held for LBI Boyd recovery (open follow-up).","doingTs":"2026-08-20T18:14:40.032Z","ageH":1.1750527777777777,"eli12":"This task is fixing up a group of 223 product codes so they match the original ones and find the real manufacturer numbers from another source. So far, most of them are fixed and working correctly, but a few are still waiting to be finished."},{"id":"TK-10742-enrich-sample-followup-contacts-json-fro","title":"Enrich sample-followup contacts.json from FileMaker vendor table (~25 skipped vendors)","project":"sample-followup","agent":"sample-followup-coord","doingText":"REDO complete (Steve resumed): added recently-contacted suppression (data/recently-contacted.json from info@ Sent), CATCHUP=6 covers since-8/15. Result: 26 dup drafts -> 4 clean drafts for 3 truly-outstanding vendors (Brewster, Greenland/bmwallpaper x2, Sandberg); 7 suppressed as already-emailed by team. Commit aacc67a. Nothing sent.","doingTs":"2026-08-20T17:58:55.102Z","ageH":1.4375333333333333,"eli12":"This task is about making sure we don't miss any important vendor contacts by cleaning up and updating a list of people we should follow up with. Right now, we’ve fixed some duplicates, added new info, and found a few really good contacts to focus on—though nothing has been sent out yet."},{"id":"TK-10732-crcp-city-fleet-onboard-bel-air-studio-c","title":"CRCP city fleet: onboard Bel Air/Studio City/Sherman Oaks/Encino + CA fire cities (last 5yr) as <slug>.crcp — overnight yoloforever loop, deploys gated","project":"crcp-cities","agent":"crcp-cities-onboard","doingText":"Email backfill COMPLETE for ready cities: all 4 top-120 web-enrich shards done (+42 web emails on top of 60 $0-join = 102 distinct contractor emails). LIVE: encino 113, belair 144, studiocity 15 email-linked permit rows (272 total). $0 throughout (free WebSearch/WebFetch). shermanoaks thin (keeper still scraping its licenses); each future contractor-CSV resync auto-re-runs the $0 email+phone join to grow coverage.","doingTs":"2026-08-20T17:15:20.704Z","ageH":2.163755,"eli12":"This task is trying to connect city fleet data with contractor emails and phone numbers so we can track who's working on city projects — like linking a construction company to the jobs they're doing. It's going really well right now: we've already matched a lot of emails, and it's all happening for free using online tools."},{"id":"TK-10686-carnegie-each-colorway-gets-its-own-dw-s","title":"Carnegie: each colorway gets its own DW SKU number (+ missing image/data)","project":"dw-commerce","agent":"vp-dw-commerce","doingText":"Root cause confirmed: 5932/5934 active Carnegie have custom['Color Hex']+custom.swatch_hex (space/underscore keys), only 51 have lowercase custom.color_hex. Grid card fix already present in product-list-item.liquid (reads Color Hex->swatch_hex). Remaining: color-dots.liquid PDP Tier2.5 only reads lowercase color_hex - must add Color Hex + swatch_hex.","doingTs":"2026-08-20T16:56:51.972Z","ageH":2.471736111111111,"eli12":"This task is making sure each color of the Carnegie product has its own special number and correct image. Right now, some colors are missing the right info, and we’re fixing the part of the website that shows colors so it uses the right format."},{"id":"TK-10557-approve-launch-rentv-gated-batch-11-wind","title":"Approve + launch rentv gated batch (11 windows, Steve-authorized 2026-08-14)","project":"rentv","agent":"steve-main","doingText":"TK-10577 (monitor→close+email) is this ticket's watchdog-child, not a separate effort (Steve-noted 2026-08-20). Close both together when the batch lands.","doingTs":"2026-08-20T16:42:33.893Z","ageH":2.710091388888889,"eli12":"This task is about getting a group of 11 items ready to be used and then officially starting them, with Steve’s approval. Right now, we’re waiting for the final step to happen, and when it does, we’ll finish up both this task and another one that’s linked to it."},{"id":"TK-10653-dw-shopify-dead-target-redirect-scan-bro","title":"DW Shopify dead-TARGET redirect scan: broken-redirect (lands-on-404) detection + total prune-headroom for TK-10640 (read-only)","project":"dw-unified","agent":"redirect-fix","doingText":"Cost: $0 (Shopify Admin GraphQL reads/writes + storefront curl HEADs; no paid API). Live cap 100,000 -> 99,648. Memo updated in pending-approval with corrected split (6,176 REPOINT / 41,120 PRUNE) + paste-lines + restore-map path.","doingTs":"2026-08-20T07:34:03.298Z","ageH":11.851923333333334,"eli12":"This task is checking for broken links on a Shopify store that should lead to a specific page but instead show an error, and it's figuring out how many of those broken links can be safely removed. Right now, it's working well with no extra costs, and it's almost done checking all the links."},{"id":"TK-10463-kamatera-disk-chronically-full-99-backup","title":"Kamatera disk chronically full (99%) — backup-retention + .git bloat fix (root cause of TK-10461 PG crash)","project":"infra","agent":"kamatera-resize","doingText":"Recommend 1000G target: 626G used -> ~340G free/~64%. growpart /dev/sda 2 && resize2fs /dev/sda2 online, no downtime. Snapshot-first advised (quiesce PG if possible).","doingTs":"2026-08-20T06:35:48.497Z","ageH":12.82270138888889,"eli12":"We're trying to free up space on the computer's hard drive because it's almost full, which caused a problem. We're suggesting making the drive bigger and cleaning up old backups and big files to make more room."},{"id":"TK-10629-reboot-exo-cluster-google-merchant-agent","title":"Reboot exo-cluster + google-merchant-agent; verify exo interconnect (cables in)","project":"fleet-ops","agent":"claude@w21t0p2:F461DDA8-59D5-4F41-B9FC-7556CA9A1941","doingText":"FINAL: confirmed 0 ACTIVE 'Lillian August' leaks (16 lillian-search hits are FP: Lilliana/Lilliane/Lillian's pattern names). Versa scrub complete + verified. Ticket work done.","doingTs":"2026-08-20T06:25:29.394Z","ageH":12.994674444444444,"eli12":"We're restarting two important systems to make sure they're working properly, and checking the connections between them like making sure all the cables are plugged in. Everything looks good so far—no problems found, and the cleanup work is done."},{"id":"TK-10722-onboard-beverly-hills-beverlyhills-crcp","title":"Onboard Beverly Hills — beverlyhills.crcp.agentabrams.com, clone of Altadena template (dev/construction lending-intel, not fire)","project":"beverlyhills","agent":"beverlyhills-onboard","doingText":"LIVE + verified: beverlyhills.crcp.agentabrams.com 401->200, LE cert, /api/stats real (5101 profiles/$20.3B collateral), dossier works. Port-collision (9920/9922 taken) fixed->9924.","doingTs":"2026-08-20T06:25:18.106Z","ageH":12.99781,"eli12":"We’re setting up a new website for Beverly Hills that works just like the one for Altadena, but for a different kind of lending. Right now, the site is up and running smoothly, and we fixed a small problem with the computer ports so everything works correctly."},{"id":"TK-10636-ga4-install-29-live-but-untagged-portfol","title":"GA4 install: 29 live-but-untagged portfolio sites","project":"ga-allsites","agent":"ga4-inject","doingText":"Delivered final report to Steve: 0 injections executed, 18 HOLD with reasons. Cost: /bin/zsh (local probes only).","doingTs":"2026-08-20T06:22:23.249Z","ageH":13.04638138888889,"eli12":"This task is trying to add tracking to 29 websites so we can see how people use them. Right now, nothing has been done yet, and 18 of the sites are on hold for different reasons."},{"id":"TK-10718-onboard-altadena-eaton-fire-rebuild-alta","title":"Onboard Altadena (Eaton Fire rebuild) — altadena.agentabrams.com, clone of Palisades data product","project":"altadena","agent":"altadena-onboard","doingText":"Steve DECLINED LA Assessor owner-data inquiry: no owner names, Altadena stays owner-less/compliance-favorable. Inquiry NOT sent.","doingTs":"2026-08-20T05:43:58.003Z","ageH":13.12065138888889,"eli12":"This task is about setting up a new website for Altadena, similar to one already used in Palisades, to help with fire recovery. Right now, Steve said no to getting owner names from the LA Assessor, so Altadena will stay without owner info, which keeps things simpler and more compliant."},{"id":"TK-10652-mac2-process-liveness-hunt-internal-dw-d","title":"Mac2 process-liveness hunt: internal-dw-dns crash-loop + flapping launchd tunnels (silent-degradation, pm2 says online)","project":"operations","agent":"keepalive-fix","doingText":"Refined per Codex review (commit 27a7514): probe now reports reason so a HUNG HTTP server (code=0/timeout) still restarts while a non-HTTP listener (code=0/error, tcp-alive) skips. Full 6-case matrix verified + live pass clean. item#1 root-cause DONE. Two commits: 5a9390b + 27a7514, reversible.","doingTs":"2026-08-20T06:17:50.235Z","ageH":13.12221861111111,"eli12":"This task is trying to fix a problem where a server on a Mac sometimes crashes and keeps restarting, making it unreliable. Right now, the fix is working well—it correctly restarts when needed and avoids unnecessary restarts, so things are more stable."},{"id":"TK-10716-palisades-permit-contractor-phone-email","title":"Palisades permit contractor phone+email enrichment (7236 permits)","project":"palisades-data","agent":"palisades-scraper","doingText":"Remaining email residual (~221 firms w/ website unresolved + firms w/o any website) would need openclaw website-discovery (serial, single browser) + optional Codex precision pass (paid) — separate future run, not this max-it. Phone gap (768) is CSLB-empty, not enrichable by re-scrape.","doingTs":"2026-08-20T05:50:48.557Z","ageH":13.572684722222222,"eli12":"This task is trying to find and fill in missing phone and email info for 7,236 construction permits. Right now, we’re close but still need to find websites for about 221 companies to get more emails, and we’re missing 768 phone numbers because they’re not available online."},{"id":"TK-10717-stout-monthly-all-vendor-overnight-cron","title":"Stout->monthly + all-vendor overnight cron revival (stage-only)","project":"dw-commerce","agent":"sec-triage","doingText":"","doingTs":"2026-08-20T05:47:41.682Z","ageH":13.624594444444444,"eli12":"This task is trying to get a computer program to run smoothly every month, using all the right tools, and it's just starting now—so far, nothing has happened yet. It's like getting a robot ready to do a job every month, but it's still sitting there waiting for the first instruction."},{"id":"TK-10702-gmc-feed-degradation-fix-orphan-sweep-fe","title":"GMC feed degradation fix: orphan sweep + feed price resync + weight backfill + off-feed publish (canary-gated)","project":"dw-unified","agent":"steve-main","doingText":"Confirmed no live session/process running GMC full-feed (parked at go-gate ~2.5h). Steve directive: retire all other top-10 pending items + never-run. Moved 6 memos to _never/ (chargeandexplore/hollywood/dwhd/palisades-maxit/kamatera-disk/GRS-dedup), 3 shipped memos to _done/ (osborne x2, altadena-golive). Held Fabricut+Carnegie (live agents mid-write) pending Steve go on stand-down.","doingTs":"2026-08-20T05:28:20.352Z","ageH":13.947186111111112,"eli12":"This task is fixing some problems with how data is being fed into the system, like cleaning up old stuff, making sure prices are correct, and getting things back in sync. Right now, we’ve paused the main process, cleaned up some old tasks, and are waiting for final approval before moving forward."},{"id":"TK-10643-cta-cross-browser-filter-control-test-ju","title":"CTA: cross-browser filter control test — just-listed.html + mls.html (uMax, uMin, yMin, yMax)","project":"commercialrealestate","agent":"cta","doingText":"MLS investigation: yMax clear alone at 3 rows is CORRECT BEHAVIOR — the 3 rows that survive all 4 filters have year_built values that are null/empty (shown as — in the table), so yMax has nothing to filter out. The units and year columns are not showing in the table because column-manager.js uses a ColumnManager.init() wrapper path. Chips work fine, no console errors. Asset class chip click stays at 200 because the first chip (Single-Family) has 19843 listings but the server returns only 200 rows, and the chip is a subset/pre-filtered view.","doingTs":"2026-08-17T23:13:10.105Z","ageH":32.07429666666667,"eli12":"This task is checking how a website’s filters work across different web browsers, especially when dealing with listings that have missing or empty data. Right now, everything is working as expected—filters are behaving correctly, even with empty data, and there are no errors showing up."},{"id":"TK-10655-cost-ledger-spend-anomaly-audit-top-spen","title":"Cost-ledger spend-anomaly audit: top spenders + recent-spike detection across ~/.claude/cost-ledger.jsonl (332k entries)","project":"operations","agent":"dre-enrich","doingText":"CRCP mls admin default 4-unit multifamily + filter-persist-on-every-change + restore-on-load + contact columns (date/address/broker/firm/phone/email/website) shipped + headless-verified (0 errors, 238 rows populate). Commit d1b8c3d.","doingTs":"2026-08-18T20:12:33.440Z","ageH":47.21021722222222,"eli12":"This task is checking for unusual spending patterns in a big list of costs to spot any unexpected increases or big spenders. Right now, it's working smoothly, with no errors found and a lot of useful data already processed."},{"id":"TK-10573-fix-redo-last-5-days-dw-ig-posts-name-co","title":"Fix + redo last 5 days DW IG posts — name+color+SKU, strip http links","project":"dw-marketing","agent":"dw-instagram","doingText":"COORDINATION: Steve designated THIS window (batch-09 session) as the SINGLE sequential deleter for the remaining GATE-2 batches, to eliminate concurrent-shared-browser ban risk. Batch 09 done (10/10). 74 posts remain across 21 accounts (batches 01-08,10). This window will delete ONE account at a time, each gated on Steve logging openclaw into that account. Other batch windows (01-08,10): STAND DOWN on --go to avoid concurrent deletes — this window owns execution now. No delete process currently running; openclaw last on @retrowalls (done).","doingTs":"2026-08-18T05:34:38.513Z","ageH":61.84214138888889,"eli12":"This task is fixing and redoing the last 5 days of Instagram posts so they have the right product names, colors, and numbers, and removing any website links. Right now, we’re carefully deleting old posts one by one to avoid getting banned, and the next step is to fix the remaining posts."},{"id":"TK-10612-ig-delete-batch-04-10-grassclothwallpape","title":"IG delete batch 04/10 — @grassclothwallpaper + @hollywoodwallcoverings (8 posts) — TK-10573 GATE2","project":"dw-marketing","agent":"dw-instagram","doingText":"CANARY SUCCESS: Graph-API DELETE on Db55_9RG1nJ -> HTTP 200 success, deleted_id 17904217761531860, verify GET confirms GONE. API deletion IS supported for these accounts. Browser-free + no concurrency ban risk. 1/8 deleted. HOLDING for Steve go on remaining 7 (grasscloth: Db8lWzfmxGQ, Db9eGUTnT75, DcADArnAXOo; hollywood: Db6eO2ZDzxY, Db8lYa-m5d_, Db9iIhAncLa, DcAHCo6gYUf).","doingTs":"2026-08-17T16:13:25.571Z","ageH":75.19573638888889,"eli12":"This task is trying to delete 8 old posts from two Instagram accounts. So far, 1 post has been successfully deleted, and the rest are waiting for Steve to take over and finish the job."},{"id":"TK-10613-ig-delete-batch-05-10-linenwallpaper-met","title":"IG delete batch 05/10 — @linenwallpaper + @metallicwallpaper (8 posts) — TK-10573 GATE2","project":"dw-marketing","agent":"dw-instagram","doingText":"🛑 HOLD — do NOT run --canary/--go. Steve designated TK-10614 (batch-06) as the SOLE IG-delete driver on the shared openclaw Chrome. Stand down until batch-06 posts 'DRIVER RELEASED' on TK-10573. Concurrent deletes = fleet-ban vector.","doingTs":"2026-08-17T15:54:56.388Z","ageH":75.50384277777778,"eli12":"This task is trying to delete some posts from Instagram. Right now, it's on hold because another task is in charge of doing the deleting, and we need to wait for it to say it's done before we can move forward."},{"id":"TK-10611-ig-delete-batch-03-10-fabric-fridays-gol","title":"IG delete batch 03/10 — @fabric_fridays + @goldleafwallpaper (8 posts, incl 1 archived orphan) — TK-10573 GATE2","project":"dw-marketing","agent":"dw-instagram","doingText":"🛑 HOLD — do NOT run --canary/--go. Steve designated TK-10614 (batch-06) as the SOLE IG-delete driver on the shared openclaw Chrome. Stand down until batch-06 posts 'DRIVER RELEASED' on TK-10573. Concurrent deletes = fleet-ban vector.","doingTs":"2026-08-17T15:54:56.175Z","ageH":75.50390194444445,"eli12":"This task is trying to delete some old social media posts. Right now, it's on hold because another task needs to finish first to avoid getting blocked."},{"id":"TK-10374-crash-recovery-relaunch-10-in-flight-tic","title":"Crash-recovery: relaunch 10 in-flight ticket worktrees under /yoloforever (DTD+Cody), rolling window, gates→pending-approval","project":"ops","agent":"crcp-agent","doingText":"crcp-agent /yoloforever 4h-cron heartbeat (2026-08-17): idle. Inbox empty, zero owned tickets, no assignable {commercialrealestate} ticket. HEALTH GREEN: healthz 200, industry-grade=1, drift IN_SYNC driftCount=0. Cron f0527ae4 owns cadence — no ScheduleWakeup. $0 local.","doingTs":"2026-08-17T11:43:13.976Z","ageH":79.69895722222222,"eli12":"This task is trying to restart 10 ticket-related processes that were in progress but stopped, and check if they’re working properly again. Right now, everything looks calm and ready to go — no problems found, and nothing needs fixing."}]}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 712572b..7221d87 100644
--- a/public/index.html
+++ b/public/index.html
@@ -103,8 +103,18 @@ async function load(){
   try{ d = await (await fetch('/api/doing',{cache:'no-store'})).json(); }
   catch(e){ document.getElementById('clock').textContent='reconnecting…'; return; }
   document.getElementById('count').textContent = d.count;
-  document.getElementById('pend').textContent = d.pending ? ('✍️ writing '+d.pending+' plain-word summaries…') : '✅ all summarized';
-  document.getElementById('clock').textContent = 'live · as of '+new Date(d.now).toLocaleTimeString();
+  const pend = document.getElementById('pend');
+  if (d.mirror) {
+    const sa = d.syncedAt ? new Date(d.syncedAt) : null;
+    const min = sa ? Math.round((Date.now()-sa.getTime())/60000) : null;
+    const stale = min===null || min>15;
+    pend.innerHTML = sa
+      ? `🔁 Kamatera mirror · synced <b style="color:${stale?'#ff7b72':'#7ee787'}">${min} min ago</b>${stale?' (Mac2 may be offline)':''}`
+      : `🔁 Kamatera mirror · <b style="color:#ff7b72">no snapshot yet</b>`;
+  } else {
+    pend.textContent = d.pending ? ('✍️ writing '+d.pending+' plain-word summaries…') : '✅ all summarized';
+  }
+  document.getElementById('clock').textContent = (d.mirror?'mirror · ':'live · ')+'as of '+new Date(d.now).toLocaleTimeString();
   const list = document.getElementById('list');
   if(!d.items.length){ list.innerHTML='<div class="empty">Nothing is doing right now. 🎉</div>'; return; }
   list.innerHTML = d.items.map(i=>`
diff --git a/publish-snapshot.js b/publish-snapshot.js
new file mode 100644
index 0000000..c52fb4e
--- /dev/null
+++ b/publish-snapshot.js
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+// publish-snapshot.js  (runs on Mac2 — the source of truth)
+// Bakes a COMPLETE snapshot of the doing board (tickets + already-computed
+// plain-word summaries) from the live local server, then rsyncs just that one
+// small JSON up to Kamatera. Kamatera serves it verbatim — no ticket log, no
+// Ollama needed there. Run every few minutes via launchd.
+
+const http = require('http');
+const fs   = require('fs');
+const path = require('path');
+const { execFileSync } = require('child_process');
+
+const LOCAL  = 'http://127.0.0.1:9790/api/doing';
+const USER   = process.env.BASIC_USER || 'admin';
+const PASS   = process.env.BASIC_PASS || 'DW2024!';
+const SNAP   = path.join(__dirname, 'data', 'snapshot.json');
+const REMOTE = 'root@45.61.58.125:/root/Projects/doing-viewer/data/snapshot.json';
+
+function getLocal() {
+  return new Promise((resolve, reject) => {
+    const auth = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64');
+    http.get(LOCAL, { headers: { Authorization: auth }, timeout: 20000 }, (res) => {
+      let d = ''; res.on('data', c => d += c);
+      res.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(e); } });
+    }).on('error', reject).on('timeout', function () { this.destroy(); reject(new Error('timeout')); });
+  });
+}
+
+(async () => {
+  const data = await getLocal();
+  const snap = {
+    syncedAt: new Date().toISOString(),
+    source: 'mac2',
+    count: data.count,
+    items: data.items.map(i => ({          // strip nothing — summaries baked in
+      id: i.id, title: i.title, project: i.project,
+      agent: i.agent, doingText: i.doingText, doingTs: i.doingTs,
+      ageH: i.ageH, eli12: i.eli12 || null,
+    })),
+  };
+  fs.writeFileSync(SNAP, JSON.stringify(snap));
+  // rsync the single file (atomic on the remote via --inplace off / temp+rename default)
+  execFileSync('rsync', ['-az', '--timeout=25', SNAP, REMOTE], { stdio: 'inherit' });
+  const summarized = snap.items.filter(i => i.eli12).length;
+  console.log(`[${snap.syncedAt}] published ${snap.count} doing (${summarized} summarized) -> Kamatera`);
+})().catch(e => { console.error('publish failed:', e.message); process.exit(1); });
diff --git a/server-mirror.js b/server-mirror.js
new file mode 100644
index 0000000..10cbdfd
--- /dev/null
+++ b/server-mirror.js
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+// server-mirror.js  (runs on KAMATERA — always-on public face)
+// Serves the board purely from data/snapshot.json pushed by Mac2. No ticket log,
+// no Ollama, no lib.js. If Mac2 stops syncing, it keeps serving the last snapshot
+// with a staleness flag the UI surfaces. Same front-end as the primary.
+
+const http = require('http');
+const fs   = require('fs');
+const path = require('path');
+
+const PORT      = process.env.PORT || 9802;
+const SNAP      = path.join(__dirname, 'data', 'snapshot.json');
+const INDEX     = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
+const AUTH_USER = process.env.BASIC_USER || 'admin';
+const AUTH_PASS = process.env.BASIC_PASS || 'DW2024!';
+
+function authed(req) {
+  const h = req.headers['authorization'] || '';
+  if (!h.startsWith('Basic ')) return false;
+  const [u, p] = Buffer.from(h.slice(6), 'base64').toString().split(':');
+  return u === AUTH_USER && p === AUTH_PASS;
+}
+function readSnap() {
+  try { return JSON.parse(fs.readFileSync(SNAP, 'utf8')); }
+  catch { return { syncedAt: null, source: 'mac2', count: 0, items: [] }; }
+}
+
+http.createServer((req, res) => {
+  // health check is open (no auth) so deploy.sh smoke test + monitors work
+  if (req.url === '/api/healthz') {
+    const s = readSnap();
+    const ageMin = s.syncedAt ? (Date.now() - new Date(s.syncedAt).getTime()) / 60000 : null;
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    res.end(JSON.stringify({ ok: true, mirror: true, count: s.count, syncedAt: s.syncedAt, syncAgeMin: ageMin }));
+    return;
+  }
+  if (!authed(req)) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="doing-viewer (mirror)"' });
+    res.end('auth required'); return;
+  }
+  if (req.url === '/api/doing') {
+    const s = readSnap();
+    res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
+    res.end(JSON.stringify({
+      now: new Date().toISOString(), count: s.count, pending: 0,
+      mirror: true, syncedAt: s.syncedAt, items: s.items,
+    }));
+    return;
+  }
+  if (req.url === '/' || req.url === '/index.html') {
+    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+    res.end(INDEX); return;
+  }
+  res.writeHead(404); res.end('not found');
+}).listen(PORT, () => console.log(`doing-viewer MIRROR on :${PORT}`));

← 4e2d1e3 doing-viewer: add Basic Auth (admin/DW2024!) gate  ·  back to Doing Viewer  ·  doing-viewer: col 4 — why-slow + restart verdict (age + last 802a86f →