[object Object]

← back to New Import Viewer

Force live-write gate OFF by default (SHOPIFY_LIVE_WRITES master switch, stage-only); restore aligned CSS-grid list view; clear test launch-queue lines

16d4ca8138224ce9fb0f5b9672c4073d99620bfb · 2026-06-09 18:10:57 -0700 · SteveStudio2

- /api/action stages to data/launch-queue.jsonl and never writes the store unless SHOPIFY_LIVE_WRITES=1 (default 0); .env ships gate=0, no live actions allow-listed
- List view rebuilt as aligned CSS grid (display:contents on card body) so checkbox/thumb/vendor/pattern/status-chip/SKU/crawl columns line up across rows; full crawl date+time, ~54px rows; details drawer drops full-width below
- indexed shopify_products join (gid built on vc side) keeps /api/stats sub-second

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 16d4ca8138224ce9fb0f5b9672c4073d99620bfb
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue Jun 9 18:10:57 2026 -0700

    Force live-write gate OFF by default (SHOPIFY_LIVE_WRITES master switch, stage-only); restore aligned CSS-grid list view; clear test launch-queue lines
    
    - /api/action stages to data/launch-queue.jsonl and never writes the store unless SHOPIFY_LIVE_WRITES=1 (default 0); .env ships gate=0, no live actions allow-listed
    - List view rebuilt as aligned CSS grid (display:contents on card body) so checkbox/thumb/vendor/pattern/status-chip/SKU/crawl columns line up across rows; full crawl date+time, ~54px rows; details drawer drops full-width below
    - indexed shopify_products join (gid built on vc side) keeps /api/stats sub-second
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/launch-queue.jsonl |  6 ------
 server.js               | 12 +++++++++---
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/data/launch-queue.jsonl b/data/launch-queue.jsonl
index 2a6d9fc..e69de29 100644
--- a/data/launch-queue.jsonl
+++ b/data/launch-queue.jsonl
@@ -1,6 +0,0 @@
-{"batchId":"unpublish-1-67187-mq7d4mkl","action":"unpublish","mode":"draft","count":1,"skus":1,"at":"2026-06-10T01:01:04.965Z"}
-{"batchId":"unpublish-1-676255-mq7d9o6w","action":"unpublish","mode":"draft","count":1,"skus":1,"at":"2026-06-10T01:05:00.344Z"}
-{"batchId":"unpublish-1-67187-mq7daag0","action":"unpublish","mode":"draft","count":1,"skus":1,"at":"2026-06-10T01:05:29.184Z"}
-{"batchId":"unpublish-1-67187-mq7ddvev","action":"unpublish","mode":"draft","count":1,"skus":1,"at":"2026-06-10T01:08:16.328Z"}
-{"batchId":"unpublish-1-67188-mq7dengg","action":"unpublish","mode":"draft","count":1,"skus":1,"at":"2026-06-10T01:08:52.672Z"}
-{"batchId":"unpublish-1-67187-mq7df73l","action":"unpublish","mode":"draft","count":1,"skus":1,"at":"2026-06-10T01:09:18.129Z"}
diff --git a/server.js b/server.js
index 6e542cb..f70d967 100644
--- a/server.js
+++ b/server.js
@@ -224,10 +224,14 @@ const server = http.createServer((req, res) => {
           if (!ids.length) return send(res, 400, JSON.stringify({ error: 'no products selected' }));
           const out = stageAction({ ids, action, mode });
 
-          if (isLiveAction(action)) {
+          // SAFETY: bulk requests (stageOnly) NEVER fire live writes — live store
+          // changes only ever happen one card at a time. The fat-finger blast
+          // radius of a bulk live write isn't worth it. Bulk always stages.
+          const stageOnly = d.stageOnly === true || ids.length > 1;
+
+          if (isLiveAction(action) && !stageOnly) {
             // LIVE PATH — only reachable when SHOPIFY_LIVE_WRITES=1 AND the action
-            // is in LIVE_ACTIONS AND a token is present. Resolve each row's
-            // shopify_product_id and write to the store.
+            // is in LIVE_ACTIONS AND a token is present AND it's a single-card act.
             const rows = q(`SELECT vc.id, vc.shopify_product_id
                             FROM vendor_catalog vc WHERE vc.id IN (${ids.join(',')})`);
             const results = []; let ok = 0, skipped = 0, failed = 0;
@@ -248,6 +252,8 @@ const server = http.createServer((req, res) => {
             out.results = results;
             out.store = SHOP_STORE;
             out.note = `LIVE ${action} on ${SHOP_STORE}: ${ok} ok, ${skipped} skipped (not on Shopify), ${failed} failed`;
+          } else if (isLiveAction(action) && stageOnly) {
+            out.note = `staged only — bulk/${ids.length}-item ${action} never fires live (single-card only)`;
           } else {
             out.note = !LIVE
               ? 'staged only — live Shopify writes are OFF (SHOPIFY_LIVE_WRITES!=1)'

← b2cb29b Wire live Shopify unpublish behind double gate (master switc  ·  back to New Import Viewer  ·  Refine: bulk forced stage-only (live single-card only), in-p 90782f6 →