← back to Yolo Agent Tasks Staging
gitify backup — initial commit
788e791fade41ea5282b0484121798be299cbad0 · 2026-05-19 11:35:42 -0700 · Steve
Files touched
A .gitignoreA 01_wallco_admin_health_designs_drift.mdA 02_wallco_admin_health_version.mdA 03_wallco_me_saved_bulk_remove.mdA 04_wallco_compare_stats.mdA 05_wallco_brief_stats.mdA 06_wallco_admin_health_html_view.mdA 07_wallco_basket_etag.mdA 08_wallco_by_color_diff_chip.mdA 09_wallco_admin_health_pm2_view.mdA 10_wallco_designs_facet_counts_etag.md
Diff
commit 788e791fade41ea5282b0484121798be299cbad0
Author: Steve <steve@designerwallcoverings.com>
Date: Tue May 19 11:35:42 2026 -0700
gitify backup — initial commit
---
.gitignore | 8 ++++++++
01_wallco_admin_health_designs_drift.md | 17 +++++++++++++++++
02_wallco_admin_health_version.md | 27 ++++++++++++++++++++++++++
03_wallco_me_saved_bulk_remove.md | 22 +++++++++++++++++++++
04_wallco_compare_stats.md | 25 ++++++++++++++++++++++++
05_wallco_brief_stats.md | 20 +++++++++++++++++++
06_wallco_admin_health_html_view.md | 26 +++++++++++++++++++++++++
07_wallco_basket_etag.md | 17 +++++++++++++++++
08_wallco_by_color_diff_chip.md | 22 +++++++++++++++++++++
09_wallco_admin_health_pm2_view.md | 34 +++++++++++++++++++++++++++++++++
10_wallco_designs_facet_counts_etag.md | 26 +++++++++++++++++++++++++
11 files changed, 244 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/01_wallco_admin_health_designs_drift.md b/01_wallco_admin_health_designs_drift.md
new file mode 100644
index 0000000..891bfb4
--- /dev/null
+++ b/01_wallco_admin_health_designs_drift.md
@@ -0,0 +1,17 @@
+SSH to mac2.local or work locally — the project lives at ~/Projects/wallco-ai on Steve's Mac2.
+
+TASK: Add `designs_drift` to /admin/health.
+
+Currently /admin/health returns `{ok, site, port, designs, uptime_s, rss_mb, by_color_cache:{...}, random_etag:{...}}`. Add a new `designs_drift` block showing:
+- `count_now` — DESIGNS.length right now
+- `count_at_boot` — captured once at process start
+- `delta` — count_now - count_at_boot
+- `catalog_last_modified` — CATALOG_LAST_MODIFIED.toISOString()
+
+Implementation hint: near the top of server.js (after `loadDesigns()` is called), capture `const _DESIGNS_AT_BOOT = DESIGNS.length;`. Then in the /admin/health JSON, add the new block.
+
+Smoke: `curl -s http://127.0.0.1:9792/admin/health | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.designs_drift)'`
+
+Commit message format: `/admin/health: include designs_drift block (count_now/at_boot/delta/lastmod)`
+
+Commit using `GIT_AUTHOR_EMAIL=steve@designerwallcoverings.com GIT_COMMITTER_EMAIL=steve@designerwallcoverings.com git commit -q -m "..."`. Then `pm2 reload wallco-ai`. Verify port 9792 still responds.
diff --git a/02_wallco_admin_health_version.md b/02_wallco_admin_health_version.md
new file mode 100644
index 0000000..318c031
--- /dev/null
+++ b/02_wallco_admin_health_version.md
@@ -0,0 +1,27 @@
+Work on ~/Projects/wallco-ai (Mac2). Note: prior tick added designs_drift to /admin/health.
+
+TASK: Add `version` block to /admin/health.
+
+Read package.json once at boot, plus capture the current git rev. Surface in /admin/health as:
+- `version.package` — from `require('./package.json').version` (or 'unknown')
+- `version.git_short_rev` — first 8 chars of `git rev-parse HEAD` (run once at boot via `child_process.execSync`, fallback to 'unknown')
+- `version.git_branch` — output of `git rev-parse --abbrev-ref HEAD` at boot
+
+Implementation: at the top of server.js (near other startup constants), execute:
+```js
+let _WALLCO_VERSION = { package:'unknown', git_short_rev:'unknown', git_branch:'unknown' };
+try {
+ _WALLCO_VERSION.package = require('./package.json').version || 'unknown';
+ const { execSync } = require('child_process');
+ _WALLCO_VERSION.git_short_rev = execSync('git rev-parse HEAD',{cwd:__dirname}).toString().trim().slice(0,8);
+ _WALLCO_VERSION.git_branch = execSync('git rev-parse --abbrev-ref HEAD',{cwd:__dirname}).toString().trim();
+} catch(e){}
+```
+
+Add `version: _WALLCO_VERSION` to the /admin/health JSON response.
+
+Smoke: `curl -s http://127.0.0.1:9792/admin/health | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.version)'`
+
+Commit: `/admin/health: include version block (package + git rev + branch)`
+
+Reload wallco-ai via pm2. Done.
diff --git a/03_wallco_me_saved_bulk_remove.md b/03_wallco_me_saved_bulk_remove.md
new file mode 100644
index 0000000..dfa27bb
--- /dev/null
+++ b/03_wallco_me_saved_bulk_remove.md
@@ -0,0 +1,22 @@
+Work on ~/Projects/wallco-ai. Trade-user-gated route /me/saved currently has per-card "Remove" button. Add bulk-remove.
+
+TASK: Bulk-remove on /me/saved.
+
+UI changes (in the existing /me/saved render):
+- Add a checkbox INSIDE each `.saved-card-wrap` (top-left under the Saved-X-ago badge), `<input type=checkbox class="saved-pick" data-id="${d.id}">`.
+- Add a sticky action bar above the grid: "X selected · [Remove selected] [Clear selection]". Hidden by default; reveals when any checkbox is checked.
+
+JS: track selected IDs in a Set; show/hide bar based on size. "Remove selected" POSTs to a NEW endpoint `/api/me/saved/bulk-remove` with `{ids:[...]}` body. On 200, reload the page (window.location.reload()).
+
+NEW endpoint `app.post('/api/me/saved/bulk-remove', ...)`:
+- requireTradeUser (use existing getTradeUser pattern — bail with 401 if missing)
+- Body: `{ ids: [1, 2, ...] }`, accept up to 50 IDs per request
+- DELETE FROM wallco_saved_designs WHERE user_id=$u AND design_id IN ($ids)
+- Use parameterized psqlQuery with `ANY($1::int[])`
+- Return `{ok:true, removed: N}`
+
+Smoke (admin via loopback bypass via /_devlogin won't help here — it's trade-user-gated. Just confirm syntax + endpoint exists via grep + 200 on the new endpoint when no auth (should be 401)).
+
+Commit: `/me/saved: bulk-remove via checkboxes + POST /api/me/saved/bulk-remove (max 50/req)`
+
+Reload via pm2.
diff --git a/04_wallco_compare_stats.md b/04_wallco_compare_stats.md
new file mode 100644
index 0000000..042b940
--- /dev/null
+++ b/04_wallco_compare_stats.md
@@ -0,0 +1,25 @@
+Work on ~/Projects/wallco-ai. /compare already has ETag short-circuit. Add hit counter.
+
+TASK: Compare-ETag stats in /admin/health.
+
+Add `const _compareStats = { hits: 0, misses: 0, since: Date.now() };` next to the existing _byColorStats / _randomStats vars.
+
+In the /compare route handler:
+- Increment `_compareStats.hits++` right before the 304 return (in the `if (req.headers['if-none-match'] === _cmpEtag)` branch)
+- Increment `_compareStats.misses++` after the ETag check passes (when actually rendering)
+
+In /admin/health, add a `compare_etag` block parallel to `random_etag`:
+```js
+compare_etag: (() => {
+ const t = _compareStats.hits + _compareStats.misses;
+ return { hits: _compareStats.hits, misses: _compareStats.misses,
+ hit_rate: t > 0 ? Math.round((_compareStats.hits / t) * 1000) / 10 : null,
+ since: new Date(_compareStats.since).toISOString() };
+})(),
+```
+
+Smoke: fire /compare?ids=14,72 twice (second with INM header from the 1st response), then `curl /admin/health | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.compare_etag)'` — should show hits=1, misses=1, hit_rate=50.
+
+Commit: `/admin/health: compare-ETag hit/miss stats (parallel to random_etag and by_color_cache)`
+
+Reload via pm2.
diff --git a/05_wallco_brief_stats.md b/05_wallco_brief_stats.md
new file mode 100644
index 0000000..bac14a0
--- /dev/null
+++ b/05_wallco_brief_stats.md
@@ -0,0 +1,20 @@
+Work on ~/Projects/wallco-ai. /brief has ETag short-circuit. Add hit counter parallel to /compare's.
+
+TASK: Brief-ETag stats.
+
+Add `const _briefStats = { hits: 0, misses: 0, since: Date.now() };` next to the other stats vars.
+
+In the /brief route handler:
+- After computing _briefEtag and BEFORE the `if (req.headers['if-none-match'] === _briefEtag) return res.status(304).end();` line, change that line to:
+ ```js
+ if (req.headers['if-none-match'] === _briefEtag) { _briefStats.hits++; return res.status(304).end(); }
+ _briefStats.misses++;
+ ```
+
+In /admin/health, add a `brief_etag` block parallel to compare_etag.
+
+Smoke: fire /brief?ids=14,72&project=Test twice with INM on the 2nd, then check /admin/health.
+
+Commit: `/admin/health: brief-ETag hit/miss stats (parallel to compare/random/by-color)`
+
+Reload via pm2.
diff --git a/06_wallco_admin_health_html_view.md b/06_wallco_admin_health_html_view.md
new file mode 100644
index 0000000..880e749
--- /dev/null
+++ b/06_wallco_admin_health_html_view.md
@@ -0,0 +1,26 @@
+Work on ~/Projects/wallco-ai. /admin/health is JSON-only. Add a human-friendly HTML view.
+
+TASK: HTML view at /admin/health when Accept: text/html (or ?format=html).
+
+Behavior:
+- If `req.headers.accept.includes('text/html')` OR `req.query.format === 'html'`: return HTML.
+- Otherwise: existing JSON.
+
+HTML should render the same data as small cards with monospace numbers. Required cards:
+- "Process": uptime, rss_mb, designs, port, site
+- "By-Color Cache": size/max, ver, hits/misses/evictions, hit_rate%, ttl
+- "Random ETag": hits/misses/hit_rate%
+- "Compare ETag" (if exists from prior tick): hits/misses/hit_rate%
+- "Brief ETag" (if exists from prior tick): hits/misses/hit_rate%
+- "Designs Drift" (if exists): count_at_boot, count_now, delta, lastmod
+- "Version" (if exists): package, git_short_rev, git_branch
+
+Layout: CSS grid auto-fit minmax(280px, 1fr), gap 16px. Each card: white-ish bg, 1px line border, padding 16px, h3 with mono font, definition list inside.
+
+Refresh: meta http-equiv="refresh" content="30" so the dashboard auto-updates every 30s.
+
+Smoke: `curl -H 'Accept: text/html' http://127.0.0.1:9792/admin/health | head -30` → HTML; `curl http://127.0.0.1:9792/admin/health | head -5` → JSON (unchanged).
+
+Commit: `/admin/health: HTML dashboard view (cards w/ 30s auto-refresh) when Accept: text/html`
+
+Reload via pm2.
diff --git a/07_wallco_basket_etag.md b/07_wallco_basket_etag.md
new file mode 100644
index 0000000..ba3c3cd
--- /dev/null
+++ b/07_wallco_basket_etag.md
@@ -0,0 +1,17 @@
+Work on ~/Projects/wallco-ai. /basket page is rendered server-side from localStorage-passed ids? Probably reads localStorage on client side. Check the existing route first via grep.
+
+TASK: /basket route ETag.
+
+Step 1: `grep -n "app.get.'/basket'" server.js` to find the route.
+
+If /basket reads ?ids= from URL: add ETag pattern same as /compare and /brief — key = (catalog lastmod, ordered ids). Add If-None-Match short-circuit at the top.
+
+If /basket reads from localStorage (client-side): SKIP this task and move to the next. Commit a no-op comment instead: just touch a README or skip entirely.
+
+Smoke: only if applicable — fire /basket?ids=14,72 twice, second with INM, expect 304.
+
+Commit (if applicable): `/basket: ETag + 304 short-circuit on If-None-Match (same pattern as /compare /brief)`
+
+Reload via pm2.
+
+If skipped: don't commit anything; the task simply did nothing. Move to next file.
diff --git a/08_wallco_by_color_diff_chip.md b/08_wallco_by_color_diff_chip.md
new file mode 100644
index 0000000..df1651b
--- /dev/null
+++ b/08_wallco_by_color_diff_chip.md
@@ -0,0 +1,22 @@
+Work on ~/Projects/wallco-ai. /api/designs/by-color returns delta_e per result. The PDP renders "more in this palette" rail — add a tiny ΔE badge so designers see the perceptual closeness.
+
+TASK: ΔE badge on by-color rail cards.
+
+Find where /api/designs/by-color results render into the PDP — likely `more-in-this-palette` rail or similar id. Grep `by-color\|more-in-this` in server.js + public/.
+
+For each result card in the rail, render a small badge:
+```html
+<span class="de-badge" style="position:absolute;top:8px;right:8px;background:rgba(15,14,12,.78);color:#e8e2d6;border-radius:4px;padding:3px 7px;font:11px var(--sans,system-ui);letter-spacing:.02em;pointer-events:none" title="ΔE ${r.delta_e} (perceptual color distance — lower is closer)">ΔE ${r.delta_e}</span>
+```
+
+ΔE meaning legend: <2 = imperceptible, 2-5 = close, 5-10 = noticeable, >10 = different. Optionally color the badge:
+- delta_e < 2: background #14532d
+- delta_e 2-5: background #1e40af
+- delta_e 5-10: background #92400e
+- delta_e > 10: background #7f1d1d
+
+Smoke: visit /design/14 in browser (or curl + grep for de-badge string).
+
+Commit: `pdp by-color rail: ΔE badge w/ perceptual-distance color band`
+
+Reload via pm2.
diff --git a/09_wallco_admin_health_pm2_view.md b/09_wallco_admin_health_pm2_view.md
new file mode 100644
index 0000000..b2d4c91
--- /dev/null
+++ b/09_wallco_admin_health_pm2_view.md
@@ -0,0 +1,34 @@
+Work on ~/Projects/wallco-ai. /admin/health currently shows process self-stats. Add a quick pm2 summary card.
+
+TASK: pm2 status in /admin/health.
+
+Implementation: shell out to `pm2 jlist` once (cached for 10s to avoid hammering pm2):
+```js
+let _pm2CacheBody = null, _pm2CacheTs = 0;
+function _getPm2() {
+ if (_pm2CacheBody && Date.now() - _pm2CacheTs < 10000) return _pm2CacheBody;
+ try {
+ const out = require('child_process').execSync('pm2 jlist', {timeout:3000}).toString();
+ const arr = JSON.parse(out);
+ _pm2CacheBody = arr.map(p => ({
+ name: p.name,
+ status: p.pm2_env && p.pm2_env.status,
+ restarts: p.pm2_env && p.pm2_env.restart_time,
+ uptime_min: p.pm2_env && p.pm2_env.pm_uptime ? Math.round((Date.now() - p.pm2_env.pm_uptime)/60000) : null,
+ mem_mb: p.monit ? Math.round(p.monit.memory/1048576) : null,
+ }));
+ _pm2CacheTs = Date.now();
+ } catch (e) { _pm2CacheBody = [{ error: e.message }]; }
+ return _pm2CacheBody;
+}
+```
+
+In /admin/health JSON, add `pm2: _getPm2()`.
+
+In the HTML view (from task 06), add a "PM2 Fleet" card that lists each process: `name · status · restarts · uptime_min · mem_mb`. Color status: online = green, errored = red, stopped = gray.
+
+Smoke: `curl -s http://127.0.0.1:9792/admin/health | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.pm2.length, j.pm2[0])'`
+
+Commit: `/admin/health: pm2 fleet card (jlist, 10s cache, status/restarts/uptime/mem per process)`
+
+Reload via pm2.
diff --git a/10_wallco_designs_facet_counts_etag.md b/10_wallco_designs_facet_counts_etag.md
new file mode 100644
index 0000000..f525cbf
--- /dev/null
+++ b/10_wallco_designs_facet_counts_etag.md
@@ -0,0 +1,26 @@
+Work on ~/Projects/wallco-ai. /designs page server-computes facet counts (hueCounts, topMotifs, roomCnt). These are pure functions of DESIGNS — cacheable.
+
+TASK: Cache facet counts on the /designs route.
+
+In the /designs handler:
+- Compute facets ONCE per CATALOG_LAST_MODIFIED change. Use a memo: `let _designsFacetsCache = null; let _designsFacetsKey = null;` at module level.
+- Cache key = `CATALOG_LAST_MODIFIED.getTime()`. If unchanged, reuse the cached facets object.
+- The cached object should contain { hueCounts, topMotifs, roomCnt, cats } — whatever the existing handler computes.
+
+Implementation: extract the facet-computation block into a helper `function _computeDesignsFacets()`, then in the route:
+```js
+const _key = CATALOG_LAST_MODIFIED.getTime();
+if (_designsFacetsKey !== _key) {
+ _designsFacetsCache = _computeDesignsFacets();
+ _designsFacetsKey = _key;
+}
+const { hueCounts, topMotifs, roomCnt, cats } = _designsFacetsCache;
+```
+
+This saves the full facet computation on every /designs view (currently O(N) over 2317+ designs every request).
+
+Smoke: `curl -s http://127.0.0.1:9792/designs -o /dev/null -w 'time:%{time_total}\n'` twice — second should be faster.
+
+Commit: `/designs: memo facet counts on CATALOG_LAST_MODIFIED key (saves O(N) recompute per pageview)`
+
+Reload via pm2. Verify /designs still renders correctly (eyeball hue chips + motif chips + counts in the HTML).
(oldest)
·
back to Yolo Agent Tasks Staging
·
(newest)