Replaces the buyer-name fuzzy match with Sharavana's real model. The plan is chosen
by value-add category × fabric type; lead time is deterministic from the grid; the
engine selects the real synced Stitch template ("Category N - X Days") and back-
schedules its authoritative milestones (no hardcoding — Sharavana maintains them in
Stitch, we re-sync). services/tna_catalog.py holds only what's not in Stitch: the
(category × fabric) → tier grid + the WFX→category/fabric pre-fill guessers.
- recommend(): select "Category {N} - {gridLead} Days" mirror template, schedule_backward
its items; encoded catalog kept only as a fallback if a template hasn't synced.
- _resolve_order_context: returns garment/category/fabric pre-fill guesses from WfxOrder.
- POST /tna/draft: takes category/fabric_type/garment (defaulting to the guesses);
push builds from the selected mirror template. TnaRecommendation gains
category/fabric_type/garment (self-healed).
- Frontend TnaCreate: two dropdowns (value-add, fabric) + auto-derived lead, replacing
the template picker + free-text lead box.
- docs/plans/STATUS.md: P1/P2 progress + infra lessons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Buildable-now foundation for auto-completing TNA milestones (no live-integration
dependency), per docs/plans/tna-milestone-autocomplete.md and Pratik's 3 Jul data
reality:
- services/tna/milestone_map.py — classify each milestone by source (WFX order/PO/
GRN · Stitch production · SAPL approval · manual) + coverage (AUTO_NOW/NEEDS_QR/
MANUAL).
- services/tna/reconcile.py — idempotent, monotonic completion + delay + order-risk
(never un-ticks a done milestone; measures delay vs the frozen plan).
- Phase 2b: models/tna_approval.py (append-only approval log) + services/tna/
approvals.py; decide_recommendation now records each planner decision, feeding the
Approvals milestone auto-tick with zero ERP dependency.
- scripts/smoke_test_tna_phase0.py — 24/24 passing.
Not wired into any live sync yet; ready for Phase 1 when Stitch's TNA Task API lands.
NOT deployed (prod deploys are on hold after the 7 Jul incident).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adopt Google's Open Knowledge Format (curated markdown + YAML frontmatter)
as KAARIGAR's domain-knowledge layer — not a RAG replacement (KAARIGAR is a
tool-calling agent), but the stable, curated knowledge it needs every turn to
reason correctly about SAPL.
- New OKF bundle backend/knowledge/ (glossary, wfx-oc + tna-milestones schemas,
tna-approval rule, stitch-ids join, when-to-use tool guide), interlinked.
- Loader services/hermes/knowledge.py: frontmatter-stripped, lru_cache, fail-soft.
- agent.py injects it into system_instruction (~2.5K tokens/turn).
- Tested live (real Gemini, local DB): tool routing + delay/owner + Library
facet behavior all match the curated rules.
Also: STATUS.md updated; TNA milestone auto-complete plan added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"show me beige dresses" now applies the real Refine facets (chips +
filtered grid + count) instead of listing one item and pulsing a card.
- Library.tsx reads URL facet params (?colour=&category=&fabric=&dna=
&season=&q=) -> native facetSel selections (substring match, _->space)
- search_library emits a "Show in Library" action with those facets; one
call both answers and filters the UI. Colour matching made lenient to
match the Library's Colour facet (counts agree: beige=8).
- prompt: browse intents -> filter UI (one-line reply, no long lists,
single call); factual -> text; TNA "take me to" -> navigate+highlight.
Verified: beige dresses -> /?colour=beige&category=dress; beige pieces
-> 8; "how many late" -> text only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
search_library was semantic-only, so 'black dresses' returned fuzzy,
polluted results (8 sources for 3 real matches). Now the model extracts
attributes into structured params (category/colour/fabric_family/
design_dna/season) matched exactly against tags; colour is role-aware
(dominant Hero colour). Citations == matched set.
Verified: black dresses -> 3 (all black-dominant); navy blouses -> 2;
knit tops -> 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HERMES no longer just answers; it can drive the app. New `navigate` tool
lets the agent take the user to a page and highlight a specific item; the
backend returns actions[] with focus-aware paths (/tna?focus=order:...).
A global HermesFocusRunner scrolls to + pulses the matching
[data-hermes-focus] card once the page loads. AskHermes auto-runs the
action (cancellable). Tagged TNA order cards + Library garment cards.
Verified: "take me to the late Primark order" -> query_orders+navigate
-> /tna?focus=order:8863-1264674; broad info queries don't navigate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 of the master-brain unifier (scope-hermes-for-pratik.md §9-12).
An LLM agent whose tools are our own backend functions: the user asks in
natural language, the agent picks tool(s), they run server-side AS the
logged-in user (RBAC applies), and it answers over the real results with
clickable citations. Reuses the Market Scraper's Gemini function-calling
loop, generalised and made synchronous.
- services/hermes/tools.py: search_library + query_orders (portable,
{summary, rows, citations})
- services/hermes/agent.py: ask_hermes sync loop (never raises)
- routers/hermes.py: POST /api/hermes/ask (auth as current user)
- frontend AskHermes.tsx: floating Ask bar (⌘J) + grounded answer +
citation cards; mounted globally
Tools verified against real data locally; live Gemini answer verified on
prod (local key is 403 project-denied, same as Scraper/Concept Studio).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
History in Trends and Moodboard (and Boards) leaked across users
because those sections were scoped by department, not by user.
Rework them to per-user ownership:
- rbac: add can_see_all(), owner_scope_query(), can_access_owned()
- add created_by to TrendSearch + MoodboardDecode (boot-time self-heal);
Boards reuse existing owner_id
- scraper/moodboard/collections: stamp owner on create, scope every
list, and 404-guard every detail-GET + mutation by owner
- by-garment saved-state only reflects the caller's own boards
Library + Fabric stay public; TNA/Inbox/Review/Analytics stay
team-shared; Admins and 'all'-scope roles still see everyone's.
Verified: scripts/smoke_test_user_isolation.py 14/14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Append a 2026-06-26 section to the HERMES scope note: promote HERMES from the TNA
WhatsApp coach to the atelier's brain across the whole app (Library/Studio/Trends/
Inbox/TNA). One persona, two surfaces (proactive push + an "Ask HERMES" bar); the
TNA coaching becomes its flagship skill. Notes the agent-with-tools design (reuses
our Gemini function-calling engine), grounding + RBAC, carried-over privacy
guardrails, and a 3-phase rollout (in-app Ask + morning brief first; person-level
WhatsApp + scorecards last, HR-gated).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The demo link's fillDemo helper hard-coded real demo credentials into the frontend
bundle, so anyone with the login URL or viewing source could sign in. Remove the
link, the fillDemo helper, and its CSS. Verified the creds are no longer present in
the built bundle.
Follow-up: the demo account still exists server-side (and the creds are now in git
history) — deactivate it or rotate its password via /admin for full closure.
tsc -b + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same fit-to-page treatment: h-screen flex layout, compact custom header (drop
PageHeader), height-cap the two input slots (aspect-[3/4] -> h-36), tighten the
description/dimensions/aspect controls with Generate pinned at the bottom, and let
the result panel fill the column height. The old two aspect-[3/4] boxes pushed the
controls below the fold.
Fit smoke 3/3 PASS (scrollHeight==innerHeight at 900px, Generate visible, both
slots present). tsc -b + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Our-side value-add on the "Describe the shoot" field: ✦ Enhance prompt reads the
uploaded garment image + the user's draft and rewrites it into a precise shoot
direction tuned to how Product Studio Pro is directed (subject/model, pose & angle,
background, lighting, framing, and always "keep the garment's exact colour/pattern/
texture/details sharp").
- backend POST /api/product-studio/enhance-prompt (gated moodboard:edit): Gemini
vision (gemini-3-flash-preview, JSON out) with a product-photography art-director
system prompt; designs a default e-commerce shoot if the draft is empty; 503 if
no Gemini key.
- frontend: client.ts productStudioEnhance() + an Enhance button by the textarea
(disabled until a garment is uploaded; replaces the draft with the enhanced prompt).
Frontend-flow smoke 3/3 PASS (disabled -> enabled-on-upload -> textarea filled).
tsc -b + build + py_compile clean. Real Gemini call verified on prod (key lives there).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Output was hard-coded output_resolution:"HD" (896x1152), so texture looked soft
vs Swatch->Garment. The tool also supports 2K/4K — probed 4K = 3712x4608 (~16x
the pixels). Add a Quality selector (HD/2K/4K) defaulting to 4K; bump the backend
default HD->2K; raise the poll ceiling 120->180s and client timeout 180->300s for
the heavier 4K render. Layout still fits the page (4/4 fit smoke).
tsc -b + build + py_compile clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first native cut used two oversized aspect-[3/4] boxes (~700px tall each),
pushing the prompt/aspect/generate controls below the fold. Rework to fit-to-page:
h-screen flex layout, a compact height-capped upload preview (object-contain), a
left control column with Generate pinned at the bottom, and a result panel that
fills the column height. Drop the now-unused PageHeader import.
Fit smoke: scrollHeight==innerHeight at 900px viewport, Generate visible without
scroll. tsc -b + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the earlier iframe embed: per Ajay, it should be our SAPL frontend over
the vendor's backend, exactly like Concept Studio — not the vendor's branded page.
Live-probed the backend and confirmed the contract (and corrected the trial-quota
worry): /api/tenant-info shows isTrial:false (paid tenant, no quota); /api/edit-image
works anonymously with the image as a base64 data-URL -> requestId; poll
/api/edit-status until COMPLETED -> images[].
- backend/routers/product_studio.py: POST /api/product-studio/generate
(gated moodboard:edit) base64s the upload, submits, polls to completion,
downloads the result, persists to data/concepts (served /concepts/).
config.product_studio_base; registered in main.py.
- frontend/pages/ProductStudio.tsx: native SAPL page (upload, prompt + seed chips,
aspect ratio, generate, result, download). client.ts productStudioGenerate().
Smokes: end-to-end backend via the real vendor with a synthetic image 4/4 PASS
(851 KB PNG persisted); native-UI render 5/5 PASS (no iframe). tsc -b + build +
py_compile clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AIBrandSolutions' Product Studio Pro (flat garment + prompt -> on-model shots)
is a full async chat/canvas app with a trial session + per-browser-id quota —
not the no-auth one-shot endpoint Swatch->Garment was. A server-side proxy would
share one trial quota across all users; embedding gives each user their own
browser session/quota, needs no creds (works anonymously from the link), and the
tool sets no frame-bust headers.
- New "Product Studio" nav tab -> /product-studio (pages/ProductStudio.tsx),
gated on the moodboard module like Concept Studio: full-height iframe to the
tool + an "Open in new tab" fallback.
- docs/plans/product-studio-integration.md: reverse-mapped async contract
(edit-image -> edit-status, X-Browser-ID trial, R2 storage) for the Phase-2
native persist-to-library proxy (deferred until SAPL has a vendor tenant key).
Embed smoke 5/5 PASS (gated route, iframe->tool, nav tab, fallback link,
0 errors). tsc -b + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scanning a hangtag QR (/g/:style, the public no-login landing) redirected to
/login. Regression from the Tier-2 "More like this" work: GarmentDetailModal now
fetches getSimilar on mount, and GarmentScan reuses that modal — but
GET /garments/{id}/similar is RBAC-gated, so the logged-out scan 401'd and the
global axios interceptor hard-redirected to /login (the component .catch can't
stop the interceptor).
- GarmentDetailModal: add showSimilar prop (default true); GarmentScan passes
false so the gated call never fires on the public page.
- 401 interceptor: skip the /login redirect when on a /g/ path (defense-in-depth
so a public page handles its own failures).
Logged-out scan smoke 5/5 PASS (renders garment, stays on /g/, no /login bounce,
/similar never called, 0 errors). tsc -b + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The no-TNA lane was hard-sorted by delivery urgency (most overdue first). Add a
sort toggle with three modes:
- Most overdue (delivery date — existing default, unchanged)
- Longest waiting (most days since OC created with no TNA)
- Newest (newest-created first, true newest->oldest)
Backend orders_without_tna(sort=) + /api/tna/no-tna?sort=; undated rows always
sort last; unknown sort falls back to delivery. Frontend TNALine segmented
toggle reloads the lane on change + on Sync-now, resets the show-all fold.
Sort smoke 4/4 PASS; tsc -b + build + py_compile clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 30s WFX poll re-upserted all ~77 mirror rows every cycle — it bumped
last_synced on every row, so all rows went dirty -> 77 UPDATEs/30s
(~220k no-op writes/day) and the log said "upserted 77" regardless of change.
sync_wfx_orders now compares each incoming order to the stored row and writes
(and bumps last_synced) only for rows that actually changed; commit is skipped
when nothing differs, so steady-state cycles do 0 DB writes. Log is now honest
("N new, M changed of 77 seen", debug when none).
Safe: WfxOrder.last_synced has no onupdate and is read by nobody (the last_synced
reads in tna.py are on StitchOrder). `synced` key kept for the "Sync now" UI.
Isolated in-memory delta smoke 5/5 PASS (fresh->2 added; identical re-sync->0
writes, last_synced stable; one field change->exactly 1 changed). py_compile clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 2026-06-25 Stitch call unblocks TNA write-back: Stitch will expose a Task
Template API so our recommender can push templates back (closes the "no write-back"
gap open since 16 Jun).
- docs/plans/stitch-task-template-api.md: digest, payload contract, mapping to our
mirrored templates, action items, and the open-questions list to send Stitch.
- services/tna_export.py: map a mirrored/recommended template -> the (inferred)
push payload + validate per-task mandatories and the mandatory 4-critical /
1-sewing / 1-shipment composition. ("swing" == sewing, confirmed by the
sewing_task field already in task-template-master.)
- scripts/gen_stitch_template_sample.py -> docs/plans/stitch-task-template-sample.json:
a contract-valid sample to send back (action item #2). Sample: backward template,
23 tasks, lead 90d, validation PASS.
- STATUS: log the digest; flip the write-back blocker from red to amber.
Not wired to any live endpoint yet (awaiting Stitch's URLs + masters).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Boards landing + mosaic covers already existed; the board page itself still
used the old uniform aspect-[3/4] crop. Rebuild it as a native-ratio masonry
consistent with the Library:
- CSS multi-column masonry + dominant-colour placeholders via the shared
GarmentImage; keeps drag-to-reorder + hover-remove.
- Clicking a pin opens the shared closeup (GarmentDetailModal) with its
"More like this" strip; onSave omitted (no Save CTA on a board), onOpenGarment
fetches+maps so More-like-this still navigates.
Board smoke 7/7 PASS (14 pins masonry, varied heights, 5 cols, all decoded,
closeup w/ 6 similar thumbs, no Save CTA, 0 errors); Library smoke re-run green.
tsc -b + build clean. Library Pinterest redesign (Tiers 1-3) complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Hover-Save hero: prominent gold "Save" pill + secondary find-similar appear
top-right on card hover (replaces the old bottom-overlay +).
- Progressive infinite scroll: render a growing 24-card window of the filtered
set; an IntersectionObserver sentinel appends the next batch near the viewport;
window resets on filter/search change. Faceting stays fully client-side, so the
live facet counts keep working (backend limit/offset would break them at this
scale — revisit at thousands of items).
- Closeup: the 2-pane detail modal gains a "Save to board" CTA and a "More like
this" mini-masonry (existing getSimilar endpoint); clicking a similar thumb
swaps the closeup to that garment.
Runtime smoke extended -> 8/8 PASS (59/59 decoded, 16 heights, 6 cols, windowing
gates initial render then scroll->59, hero Save on every card, closeup
Save+More-like-this with 6 thumbs, 0 errors). tsc -b + prod build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the uniform aspect-[3/4] grid (which cropped every garment to an
identical tile) with a CSS multi-column masonry. Cards now render at their
native aspect ratio — the library's images span ratios 0.54–1.39, variety
that was being thrown away — each over a dominant-colour placeholder that
reuses the garment's first tagged colour and reserves a 3:4 box until decode.
- GarmentImage: native-ratio masonry mode + a `cover` prop so fixed-size
contexts (detail modal, similar-intent modal, selection tray) still crop.
- Fix a real lazy-load deadlock: hiding the un-decoded image with display:none
prevents `loading=lazy` from ever loading it (a hidden image is never in
viewport) -> 0 images would render. Float it at opacity-0 instead.
Runtime smoke (Playwright on the built app, API mocked to the real 59-garment
payload): 59/59 images decoded, 16 distinct heights, 6-col masonry, 0 errors.
tsc -b + prod build clean. Tier 2 (hover-Save, infinite scroll, closeup) next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New "Studio" tab (/concept): drop a garment shape + a fabric swatch, describe
the item, and generate a concept preview. Proxies SAPL's commissioned
AIBrandSolutions Swatch→Garment backend server-side (no vendor creds needed),
persists the result to data/concepts/, and serves it same-origin behind our
login/RBAC.
- backend/routers/concept.py: POST /api/concept/swatch-to-garment (moodboard:edit)
- config: swatch_to_garment_base
- main.py: mount /concepts (fresh-checkout-safe mkdir) + register router
- frontend: ConceptStudio page, "Studio" nav tab, swatchToGarment()/conceptImageUrl()
- docs: 24-Jun design-library meeting notes + STATUS entry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AI TNA recommender + Critical-Dates board/calendar + Google SSO + the
prod-readiness sweep (demo-order hiding, secret-key guard, blank-home fix,
nginx cache fix, milestone mapping) and the WFX export gap finding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plain-language one-pager of the TNA automation flow (for sharing with Pratik)
and the raw meeting transcript/notes from the 23 Jun TNA session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The running "Last updated" changelog had grown to ~44K chars on a single
line (~135 dated entries). Moved entries from 2026-06-16 and earlier into a
new docs/plans/STATUS-ARCHIVE.md (newest-first), leaving the recent ~2 weeks
in STATUS.md with a pointer link. No entries altered, just relocated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TNAAllOrders + TNALifecycle: add pt-20 so headers clear the fixed nav
- TNAAllOrders + Review: tables were in overflow-hidden wrappers that
clipped on mobile -> overflow-x-auto + min-w so they scroll
- ScraperReport: trend-report sidebar was hidden lg:flex (mobile lost
the report text) -> flex-col lg:flex-row, stacks above the grid
- Upload: form-field rows grid-cols-2 -> grid-cols-1 sm:grid-cols-2
- TNAOrderPERT: fixed 340px AI panel crushed the lanes on mobile -> now
a full-screen overlay below the nav, default-closed <768px; header wraps
Analytics/Moodboard/Boards/OrderInbox/Upload-main/AdminData were already
responsive. tsc -b + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On-device testing flagged two broken TNA views:
- TNA.tsx dashboard: filter sidebar + charts were a non-collapsing
2-col flex -> content squeezed to half width, PiecesStrip numbers
overlapping. Now flex-col lg:flex-row, sidebar w-full lg:w-52 stacks
on mobile; PiecesStrip header stacks flex-col sm:flex-row.
- TNALine.tsx zone cards: the horizontal OC->pre->PCD->prod->Dispatch
strip overflowed and overlapped labels on mobile. Strip is now
overflow-x-auto with shrink-0 sm:flex-1 ZoneBoxes (scrolls on narrow
screens, desktop unchanged). Added pt-20 so the header clears the nav.
tsc -b + VITE_API_URL=/api build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mobile:
- TopNav: collapse the 9-tab bar into a hamburger drawer below lg
(links + New garment + user/sign-out + scrim); desktop unchanged
- PageHeader: stack flex-col on mobile so title + right-side
filters/stats/tabs no longer overlap
QR (cloud):
- build_qr_payload URL-encodes the style segment so spaced style
numbers (e.g. "SA W675 A") scan cleanly
- qr.png/hangtag.png auto-heal payloads whose host != current
QR_BASE_URL (old frozen localhost/LAN QRs) -> rewritten once to the
cloud host and persisted, so existing QRs resolve from a phone
tsc -b + VITE_API_URL=/api build clean; backend py_compile OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 2-min sync_from_stitch refetched the slow STITCH-KEY integration
reference (style-plan-master ~51 pages/1010 plans + client-master) and
made one style-tna-data owner call per order every cycle, even when
nothing changed (0 new rows, identical enrichment).
- _get_integration_reference(): TTL-cache plan_map+buyer_map for 30 min,
returns a `fresh` flag
- _enrich_from_integration(): per-order owner fetch only when the
reference was just refreshed (every 30 min, catches reassignments) or
the order has no owner yet -> ~0 integration calls in steady state
- add owner_api_calls to the sync summary for visibility
- stitch_sync_loop sleeps first (lifespan already runs the initial sync;
was double-syncing at startup)
Near-real-time milestone fetch, last_synced stamping and buyer/qty
mapping still run every cycle. Buyer count 36 (client-master, full
catalogue) vs 34 (tna-dashboard-client-data, clients with TNA) is an
expected superset/subset, not drift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes#1
Scraper now runs entirely on Gemini (gemini-3-flash-preview):
- scraper_agent.py: agent loop rewritten to Gemini native function-calling
(Tool/FunctionDeclaration from the existing schema, tool_config AUTO,
function_response parts fed back, _to_jsonable arg coercion, 8192 out-tokens
+ 1024 thinking so the report arg never truncates, fail-fast if no key)
- scraper_tools._analyze_image: Pixtral -> Gemini vision (JSON, thinking_budget=0)
- same 4 tools, max_steps=10, 429-retry, 2s/5s pacing preserved
- Groq/Mistral still power garment tagging + PERT narrator (untouched)
- requires only GEMINI_API_KEY; verified live end-to-end (Zudio SS26, 14 items)
Also fixes found while running locally on a fresh clone:
- client.ts: imageUrl/scrapedImageUrl derive ASSET_ORIGIN from API BASE so
images resolve to the backend in dev; prod output byte-identical (/images,
/scraped relative, proxied by nginx)
- main.py: create data/images + data/scraped before StaticFiles mounts (mount
runs at import, before the lifespan hook) so a fresh checkout boots
- Scraper.tsx: prominent loading state (spinner + current action + elapsed
timer + "Starting agent..." panel)
Docs: STATUS.md log entry + stack/scraper references updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>