{"slug":"4square-admin","total":18,"limit":100,"offset":0,"since":null,"commits":[{"hash":"d9f2548","date":"2026-07-03 08:07:48 -0700","author":"Steve","subject":"chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)","body":""},{"hash":"8e74e15","date":"2026-06-02 09:08:27 -0700","author":"SteveStudio2","subject":"bulk endpoints: try/catch so settlement-trigger RAISE returns {blocked} not a crash","body":"publish/unpublish/delete now catch DB errors; publish of a settlement-BLOCKed design returns\n{ok:false,blocked:true,error} cleanly (the trigger RAISE no longer crashes the request).\nLets the CNCP settlement-review action buttons surface the gate honestly.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"4732fba","date":"2026-06-02 08:37:14 -0700","author":"SteveStudio2","subject":"4square admin: populate fliepaper-bugs matrix from fliepaper_bugs_cells (36 Gucci cells live under category designer-bugs; pull by cell-set, not category, so the no-FLIEPAPER rename stays intact)","body":""},{"hash":"6ffa88e","date":"2026-06-02 08:17:35 -0700","author":"SteveStudio2","subject":"4square admin: fall back to a populated source when the saved/default source is empty/invalid (old 'wallco:fliepaper-bugs' default had 0 rows → empty grid → publish/unpublish stuck disabled)","body":""},{"hash":"f9ab3a8","date":"2026-05-19 18:39:46 -0700","author":"Steve Abrams","subject":"add noreferrer to all target=_blank links","body":""},{"hash":"efd0670","date":"2026-05-19 18:39:17 -0700","author":"Steve Abrams","subject":"guard against .bak/.pre-/.orig/.swp/~ url junk","body":""},{"hash":"2537670","date":"2026-05-19 18:38:53 -0700","author":"Steve Abrams","subject":"ignore backup/editor junk patterns","body":""},{"hash":"2c2f866","date":"2026-05-18 17:42:36 -0700","author":"Steve","subject":"fix: handle fetch network failures in loadSources and loadProducts instead of leaving a stuck spinner","body":""},{"hash":"d04b35d","date":"2026-05-18 17:42:17 -0700","author":"Steve","subject":"fix: revoke CSV blob object URL after download to prevent memory leak","body":""},{"hash":"b358ef8","date":"2026-05-14 09:42:17 -0700","author":"Steve","subject":"feat: bulk tag editor for wallco rows","body":"- POST /api/bulk/tag {ids, add:[], remove:[]}\n  - 'add' is idempotent (appends only if not already in tags array; per-tag scan via CASE WHEN $2 = ANY(tags))\n  - 'remove' uses array_remove for each tag in turn\n  - returns counts of added/removed/rows-affected\n- new '+ Tag' button next to '⤓ CSV', enabled when selection > 0 AND source is editable (wallco only)\n- frontend uses two prompt() inputs for add/remove comma-lists (low-friction, no modal); blank skips\n- smoke-tested with 4 rows, verified add + remove round-trip cleanly"},{"hash":"198d58c","date":"2026-05-14 09:40:58 -0700","author":"Steve","subject":"feat: 12-chip color filter strip (right-aligned in source bar)","body":"- 12 hue buckets: black, white, grey, brown, beige, yellow, orange, red, pink, purple, blue, green\n- hexToBucket() js HSL conversion — handles low-saturation rows (b/w/grey) before hue, narrow brown/beige bands at low L / mid-low S\n- rowBucket() per kind: wallco→dominant_hex, patternbank→colors_hex[0], shopify/vintage→tag-match 'color-X'\n- click chip to filter; click × or active chip to clear; persists in component state for current session\n- grid + stat counts update reactively; load-more retains filter context note\n- gracefully empty-states when no loaded row matches the picked hue with hint to load more"},{"hash":"0598058","date":"2026-05-14 09:39:02 -0700","author":"Steve","subject":"feat: source-context header bar + CSV export of selected rows","body":"- new sub-header below controls shows: source id, editable/read-only pill, license note (italic gold) for archives\n- new POST /api/export → kind-specific column projection (wallco: dig/category/pub/hex/tags/prompt; shopify: shopify_id/handle/title/vendor/sku/status/price/tags; patternbank: pattern_id/slug/designer/category/colors_hex; vintage: handle/title/vendor/era), proper RFC-4180 escaping, attachment filename\n- new '⤓ CSV' button in controls bar, enabled while selection > 0 across ALL source kinds (including read-only ones — export is non-destructive)\n- frontend exposes license_note from API into source-bar"},{"hash":"724d1f8","date":"2026-05-14 09:37:16 -0700","author":"Steve","subject":"feat: patternbank + vintage-wallpaper archive sources","body":"- new poolPB (patternbank_archive db) + poolVW (vintage_wallpaper_archive db) with graceful safe() catch if db absent on a given machine\n- /api/sources expands left rail when archives exist:\n    Patternbank archive (1,802) + top 20 categories\n    Vintage Hannah's Treasures (494) + decade buckets\n- /api/products handles patternbank:all|cat:<c> and vintage:all|decade:<d> with same q + sort + offset semantics\n- /img/:id handles patternbank (local file → remote_url fallback) + vintage (redirect to remote src)\n- both kinds rendered readonly with internal-only license badge (PB / decade pill); deep-link button → source_url\n- new sort axes: designer/category for patternbank, era ↑↓/vendor for vintage"},{"hash":"c8d2297","date":"2026-05-14 09:34:20 -0700","author":"Steve","subject":"feat: full keyboard navigation","body":"shortcuts (when grid focused, not in search box):\n- '/' focus search\n- j/k or ↑/↓ move by one row (row = current --cols value)\n- h/l or ←/→ move by one column\n- Space or 'x' toggle selection on focused card\n- Enter open detail modal\n- 'a' select all, 'n' select none\n- 'p' publish selected, 'u' unpublish, 'd' delete (all gated by selection size and read-only flag)\n- 3-9 set density columns directly\n- '?' show shortcut cheat sheet via toast\n\nfocus ring uses --pub green color, scrolls into view smoothly, cursor index resets to 0 on grid rerender, click syncs cursor for continuity"},{"hash":"48decb7","date":"2026-05-14 09:33:23 -0700","author":"Steve","subject":"feat: card-detail modal with full metadata","body":"- new GET /api/product/:id?kind=… → full row from spoon_all_designs or shopify_products\n- frontend: dblclick (any card) or shift-click (selectable) or click (readonly) opens modal\n- 2-col layout: large image left, key/value rows right (status/sku/handle/vendor/price/tags/dimensions/prompt/etc.)\n- dominant-hex swatch chip for wallco\n- modal acts: 'Open on wallco.ai' / 'Open in Shopify Admin' deep links + inline publish/unpublish/delete buttons (wallco only)\n- Esc closes, click-backdrop closes"},{"hash":"42f74e8","date":"2026-05-14 09:31:44 -0700","author":"Steve","subject":"feat: per-card deep-link hover actions","body":"- shopify cards: 'S' = open in Shopify Admin (designer-laboratory-sandbox), '↗' = open on designerwallcoverings.com storefront\n- wallco cards: '↗' = open on wallco.ai/design/:id\n- buttons appear on hover above the meta strip, stopPropagation so click doesn't toggle selection\n- tooltip on .lbl for truncated titles"},{"hash":"a147034","date":"2026-05-14 09:30:47 -0700","author":"Steve","subject":"feat: search box + load-more pagination","body":"- /api/products accepts ?q=… → ILIKE on title/SKU/DIG/handle/tags for shopify, dig_number/category/tags for wallco\n- /api/products returns has_more boolean\n- frontend: search input (220px, debounce 240ms, esc to clear), 'Load more (N remaining)' button at bottom of flat-grid, fliepaper-bugs matrix view skips pagination (small fixed set)"},{"hash":"4f1ef4e","date":"2026-05-14 09:28:35 -0700","author":"Steve","subject":"initial scaffold: 4Square admin product browser (loopback :9906)","body":"- left-rail site picker: wallco.ai (32 categories) + DW Shopify (38 vendor/status slices)\n- right-pane grid with sort + density slider per Steve's standing rule\n- fliepaper-bugs 6x6 matrix mode + flat-grid for all other categories\n- bulk publish/unpublish/delete/recolor on wallco rows; Shopify is read-only\n- localhost-only bind, no auth (loopback == admin per wallco admin-gate)\n"}]}