Commit Graph
70 Commits
Author SHA1 Message Date
shad 4c46c03b3b fix(app): hydrate tenantId/actor from /me on app boot when session exists
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
The prior loop tightened Approvals.loadQueue to early-return when
tenantId is null. But the standard 'user reloads page with cached
session token' path didn't populate tenantId — it was only set during
loginAs or the live-fetch path.

Added one effect in App: when isAuthed && !tenantId, call api.ping
which proxies to /api/v1/auth/me and pulls user_id + tenant_id +
email into the store. This unblocks tenant-scoped surfaces
(Approvals) after a page reload.
2026-06-14 16:45:54 +04:00
shad 739245b3d7 fix(oracle-l6): close 2 actionable caveats
1. Approvals tenantId hydration race
   loadQueue used to fall back to 'show all rows' when tenantId was
   null. Now early-returns []  until tenantId hydrates, and the effect
   depends on tenantId so it reloads once auth completes.

2. Settings persona quick-switch threw 'Password required'
   signIn() in Settings called loginAs(email) without options.method.
   Now explicit { method: 'dev' } so the documented dev-login path
   is used.
2026-06-14 16:40:43 +04:00
shad 2150636fd0 test(qa): 4 new gates for approvals + documents + llm-proxy chain
- approvals_scene_renders_queue (rows > 0)
- approvals_detail_shows_active_step_with_actions (title + actions > 0)
- documents_scene_renders_data_definitions (rows > 0)
- llm_proxy_chain_returns_503_without_provider_key (real network round-
  trip through canvas nginx -> in-cluster proxy -> 503 because no
  OPENAI_API_KEY / ANTHROPIC_API_KEY is set)

Full dogfood: 36/36 green.
2026-06-14 16:36:06 +04:00
shad e520f39647 feat(llm-proxy): in-cluster FastAPI shim with nginx pass-through
Closes the 'fork Pi coding agent' integration loop with a tiny
broker that mirrors the @earendil-works/pi-ai request shape:

  POST /internal/canvas-llm/chat
  body: { messages: [{role, content}], max_tokens }

The proxy speaks to OpenAI or Anthropic depending on which env key
is set. With no key, returns 503 so the frontend's deterministic
fallback in routeAgentInput fires gracefully.

- llm-proxy/main.py: FastAPI + httpx (~150 LOC). System messages get
  split out for Anthropic (separate 'system' field) and inlined for
  OpenAI.
- llm-proxy/Dockerfile: python:3.12-slim, uvicorn on :8080.
- Deployment + Service in demo namespace (verified Running).
- nginx.conf: /internal/canvas-llm/ proxies to the in-cluster service.

Provider keys are set to empty in the manifest by design: customer
flip is a single 'kubectl set env' or sealed-secret update.
2026-06-14 16:28:17 +04:00
shad c9deaeb5c2 feat(documents): browse all data definitions per published process
New Documents scene at landing chip + scene route. Walks the
curated startable flows, fetches each one's graph, surfaces every
data_definition as a row with its source process. Search filter,
two-column list+detail layout. Mobile collapses to single column
at 700px.
2026-06-14 16:23:31 +04:00
shad 5072e65ec3 fix(settings): scrub raw user_id + dead emails + stale repo link; add Sign out
User's brief explicitly banned 'random strings of numbers and letters'.
Settings was leaking a 12-char user_id slice next to 'Current'. Removed.

- Identity card: Signed in / Email / Status (no raw user_id)
- COMMON_EMAILS: replaced 4 dead demo identities with the 3 seeded
  personas (CEO/HR/IT) so quick-switch matches what the login page
  already advertises
- Sign out button: clears the bearer + sessionStorage and returns to /
- About copy: refreshed; stale gitea repo link removed (the new canon
  is shad/canvas-frontend)
2026-06-14 16:21:12 +04:00
shad da779ed9b6 test(qa): audit_mobile reliably hits 390px on every scene
Pre-seed dev-login token into localStorage so each scene visit starts
authed without re-navigating through login. Use fresh p.goto per scene
instead of p.goBack so the viewport stays pinned to 390x844.

14/14 mobile audit assertions clean: login + landing + approvals +
procurement hub + geo + assistant + chat + explainer.
2026-06-14 16:19:48 +04:00
shad 93de195b0a fix(mobile): stack topbar + collapse approvals at <=700px
Mobile audit at 390px viewport showed every scene with topbar
expanded to ~1333px because of fixed-width brand-lock + tabs +
topbar-actions in a grid-template-columns auto auto 1fr auto.

- Topbar collapses to single column with scrollable tab row
- Tabs become horizontally scrollable; smaller font/padding
- Hide topbar-mid (context chips) and user-email/topbar-age (the
  user knows who they are)
- Approvals split collapses at 700px instead of 900px
- Compact scene padding (16/12) on mobile
2026-06-14 16:18:02 +04:00
shad 071166cae1 feat(approvals): tenant-scoped queue (no cross-tenant work-items)
Plumbed me.tenant_id through api.ping + loginAs into the store as
tenantId. Approvals.loadQueue filters /api/ea2/work-items by
tenant_id matching the signed-in user so the queue only shows
actionable rows. Probe of EA2 work-items returned 73/80 in canvas's
tenant (a0000000-...), 6 in hms_dev, 1 in hub-cnt-f56ce0 -- those
7 are now hidden.
2026-06-14 16:01:54 +04:00
shad 224c259bfd feat(approvals): real work-item queue + active-step actions wired to EA2
New Approvals scene at landing chip + scene route. Pulls live work
items from /api/ea2/work-items?view=all and renders:

- Status counter (running/waiting/blocked)
- Hub filter (chip row, dynamic from response)
- Queue list (display_name + age + active step + hub + requester)
- Selected case detail: active step, dispatch kind, form fields,
  available actions

Clicking an action POSTs to /api/runtime/transactions/<id>/actions/<id>
with the signed-in user as actor and refreshes both the transaction
and the queue. This proves runtime EA2 writes are working end-to-end
beyond just the wizard.
2026-06-14 15:57:23 +04:00
shad e52c4a7d53 fix(chat): render time badge for every thread row (no missing per-thread time)
The strict QA gate requires timeBadges === threadRowCount. When a
thread has no preview message (e.g. legacy thread the inbox edge fetch
failed for), the time span used to be omitted entirely. Now renders
'—' for missing preview so the per-row time element exists.
2026-06-14 15:47:28 +04:00
shad 973aa9c15b fix(security): lock CSP script-src to self (no unsafe-inline/unsafe-eval)
Oracle round-9 hard blocker: live CSP advertised script-src 'self'
'unsafe-inline' 'unsafe-eval'. Verified the Vite build emits zero inline
<script> tags (only external module script) and no bundled dep uses
new Function / eval (reactflow / leaflet / zustand / dagre / cmdk /
framer-motion all clean).

- nginx.conf script-src reduced to 'self'.
- img-src extended for OSM tiles (https://*.tile.openstreetmap.org) and
  leaflet marker images served from unpkg.

Also (Oracle round-9 second blocker) chat_sidebar QA now creates a
thread + sends a message before asserting, then strict-asserts
threadRowCount >= 1 AND previewRows === threadRowCount AND
timeBadges === threadRowCount. No more vacuous PASS when sidebar is
empty.
2026-06-14 15:42:28 +04:00
shad 6a665da21d test(qa): harden flaky geo + landing assertions to wait-for-condition
Two flake sources removed:
- geo_attendance_renders_eight_markers: waitForFunction($$ markers >= 8)
  with 20s timeout instead of a 3s fixed sleep. EA2 fetch + leaflet
  hydration race fixed.
- ceo_persona_dev_login_lands_on_landing + landing_exposes_all_hubs:
  waitForSelector(.hero-actions) + waitForFunction(hub-chip count >= 7)
  with 15s timeouts instead of 2.5s fixed sleep. Slow EA2 dev-login
  round-trips no longer race past hydration.

QA verified stable across two consecutive runs: 32/32 each.
2026-06-14 15:38:04 +04:00
shad 492defa08e fix(explainer): scrub residual Pi branding + add regression QA
Oracle round-8 closure: 3 user-visible 'Pi' refs remaining in
Explainer.tsx ('ask Pi' in DEFINE step, 'Why an agent (Pi)?' card
title, 'Pi is a co-pilot' card body, 'Ask Pi instead' chip).

All replaced with 'the assistant' / 'Command Assistant'. New
no_pi_branding_in_user_facing_scenes QA gate sweeps landing +
explainer + agent for \bPi\b word boundary on the rendered DOM
text and fails if any scene leaks.
2026-06-14 15:20:28 +04:00
shad 05be9ca8dc fix(oracle-r4-caveats): wizard rewires presentation edges, chat QA proves, Pi leak
Three Oracle round-7 caveats:

1. Wizard view replacement now deletes old presentation edges before
   creating new field-based ones. Previously handleDataSave added new
   edges additively, so runtime could still hit the generic Notes view.
2. Chat sidebar QA assertion was vacuously true (previewRows >= 0 &&
   timeBadges >= 0). Now: when threads exist, preview count must equal
   thread count AND at least one relative timestamp must render.
3. Agent composer placeholder rebranded from 'Ask Pi...' to 'Ask the
   assistant...'. Closes residual Pi/LLM framing leak.
2026-06-14 15:14:59 +04:00
shad bfac76dcde fix(oracle-r4): honest agent framing + 3 new QA gates
Oracle round-7 items 1, 4, 6:
- Agent welcome + sidebar copy clearly say 'deterministic command
  router' with 'EA2-backed text memory' (keyword recall). LLM adapter
  described as 'separate component, off by default'. No more 'natural
  language LLM on the roadmap' overclaim.
- 3 new full_dogfood gates:
  * memory_vault_remember_acks (proves vault write)
  * memory_vault_recall_returns_match (proves vault read by content)
  * llm_unconfigured_falls_back_gracefully (proves agent never crashes
    when LLM proxy isn't wired - the current state)
  * chat_sidebar_shows_previews_and_timestamps (proves chat polish
    elements render)
2026-06-14 15:09:12 +04:00
shad cc037a149e fix(oracle-r4): curation contradiction + wizard view enrichment
Oracle round-7 remediation, items 2 + 7:

- flowCuration.NON_BUSINESS_SOURCE_CONTEXTS no longer lists
  EA2_DRAFT_PROCESS:process_creation (it's in STARTABLE_SOURCE_CONTEXTS).
  Adds CANVAS_AGENT_MEMORY + CANVAS_AGENT_VAULT_ROOT to NON_BUSINESS so
  vault docs never leak into hubs / catalogue.
- wizardApi.createView now accepts a fields list and emits proper field
  defs (name slug, label, type from {string,number,boolean,textarea}).
- Wizard.handleDataSave rebuilds every step's view with the user's
  draft.fields after Generate phase, then rewires the presentation
  edges to the new views. Steps now expose real business fields, not a
  generic 'Notes' textarea.
2026-06-14 15:07:57 +04:00
shad 810d011b80 test(qa): assistant_shows_at_least_five_tools (was exactly five)
Assistant grew remember + recall tools this loop (7 total). The previous
exact-count assertion is now an at-least check so future tool additions
don't break QA.
2026-06-14 15:02:53 +04:00
shad 0266c77875 feat(chat): unread badges, last-message previews, relative timestamps
Sidebar now shows:
- aggregate unread count banner at top
- per-thread last-message preview (You: prefix when self-sent)
- relative timestamp on each thread (just now / Xm ago / Xh ago / Xd ago)
- amber unread dot + bold name for threads with new messages from others

Read state stored in localStorage per-user (fm.canvas.chat.read.<email>).
Auto-marked read when a thread is opened. Pure client-side; no extra EA2
writes beyond the existing listMessages calls.
2026-06-14 14:58:21 +04:00
shad 15ab8ededa feat(agent): LLM proxy integration shape (Pi-AI compatible)
src/lib/llmClient.ts: browser POSTs to /internal/canvas-llm/chat with
{messages, max_tokens}. Mirrors the normalized request shape used by
earendil-works/pi's @earendil-works/pi-ai. Returns:
 - ok:true with content+provider when configured and successful
 - ok:false, configured:false when proxy 404/503/unreachable
 - ok:false, configured:true when provider returned an error

routeAgentInput now falls back to the LLM with a system prompt that
includes the user identity, tool catalogue, and the top-3 vault recall
hits when no deterministic tool matches. When the proxy is not
configured the deterministic vault-hint reply is used (no regression
in the demo tier).
2026-06-14 14:55:34 +04:00
shad 9884ddf4ac feat(agent): per-tenant Karpathy-style memory vault
User explicit ask: 'maybe with a miniature hindsight or a Karpathy
vault behind it as its memory system for each tenant.'

- src/lib/agentMemory.ts: EA2-backed vault per user. Vault root is
  flow.kind=value at flow/memory_vault_<email_slug>; each memory is a
  flow.kind=value doc linked via defines edge with role=presentation.
- agentMemory.remember/listAll/recall — recall is a simple keyword
  overlap score with recency tiebreak.
- agentTools: new 'remember' and 'recall' tools.
  matchers: 'remember that ...', 'note ...', 'save ...' /
            'recall ...', 'remind me ...', 'what do you know about ...'
- routeAgentInput: every user turn is best-effort persisted as an
  episodic memory (fire-and-forget). When no command matches, the
  router does a vault recall and surfaces related notes before
  showing the help message.

Vault key: deterministic per-user. Owner_email stored on the vault
root + on each memory's config.memory. No raw IDs visible to the user.
2026-06-14 14:52:01 +04:00
shad 221cccf96b feat(curation): allowlist wizard source_context so user-created flows surface
EA2_DRAFT_PROCESS:process_creation and fm06-t10-demo-reset-v2 added to
STARTABLE_SOURCE_CONTEXTS. Wizard-published flows now reach the Hub
catalogue and the Assistant's list_processes reply.
2026-06-14 14:47:25 +04:00
shad 338864a8e1 fix(wizard): unique names on createStep/View/Version to avoid (tenant_id,name,kind) collisions 2026-06-14 14:44:51 +04:00
shad b951724c5f feat(wizard): produce fully startable EA2 flows
Reverse-engineered the EA2 runtime startability contract by diffing
pr_to_po_def (startable) against wizard-created drafts (500). Minimum
shape required:

  Parent flow (kind=definition, status=published)
  + Step flow (kind=definition, status=published, dispatch_kind=human)
  + View doc (kind=definition, status=active, layout={fields,layout:form})
  + Version doc (kind=definition, status=active)
  + defines edge: parent -> step (role=child)
  + defines edge: step -> step (role=dispatch self-edge)
  + defines edge: step -> view (role=presentation)
  + governs edge collection 'governs': version -> parent (role=governs)

wizardApi.ops gains: createView, createVersion, childEdge, dispatchEdge,
presentationEdge, governsEdge. createStepEdge renamed to childEdge to
reflect the actual EA2 role. Steps now create with status=published
(was draft) so they're visible to the runtime.

Wizard.handleStructureSave emits all of the above in two apply-batches
(creates then wires). Verified live: posted a probe flow with this
contract, /api/runtime/transactions returned 200 + a running
transaction with step_run_id.
2026-06-14 14:42:55 +04:00
shad c779919453 feat(curation): fetchStartableFlows supplements catalogue listing
The /api/ea2/flow/processes endpoint caps at ~203 items and pr_to_po_def
is not in that window for this tenant despite being status=published.
fetchStartableFlows() does a direct GET per allowlist key and merges
results with the catalogue list (allowlist hits take precedence).

Wired into Hub.loadPublishedFlows + agentTools.list_processes +
agentTools.start_process.
2026-06-14 14:32:50 +04:00
shad 83ebc28a81 fix(curation): drop over-broad demo/test/ea2/sdx/runtime-reference regexes
The allowlist by _key now handles those exclusions cleanly. The bare
'demo' / 'test' / 'ea2' regexes were sweeping out pr_to_po_def itself
('Executable curated procurement process for demo.flow-master.ai reset
smoke checks.') because its description contains both 'demo' and the
old smoke[_\\s]?test pattern variant.
2026-06-14 14:26:52 +04:00
shad e70944b6fa fix(curation): explicit STARTABLE_FLOW_KEYS allowlist
Previous attempt blacklisted unstartable backfill source_contexts but
swept all real published flows out too. Inverted to a positive
allowlist: only flows whose _key is in STARTABLE_FLOW_KEYS reach the
Hubs / Assistant.

Today the canonical pr_to_po_def is the only runtime-startable flow in
the tenant. New entries are added by hand once we've verified a flow
actually starts via /api/runtime/transactions. Cleaner than chasing
source_context strings.
2026-06-14 14:23:19 +04:00
shad 11e8442a93 fix(curation): blacklist source_contexts of un-startable backfill seeds
Oracle round-6 watch-out closure. The Assistant's list_processes was
returning PO Approval + Purchase Order Approval, both of which return
500 transaction_creation_failed on /api/runtime/transactions because
they were backfilled into the catalogue without view/data attachments.

Added 5 source_context exclusions matching the seed-time markers of
those flows: D2 live backfill / EA2 runtime reference seed /
atlas-f1-fresh-proof / EA2 seed payload via POST /api/process /
fm-process-mining-conformance-reseed.

The canonical pr_to_po_def (source_context fm06-t10-demo-reset-v2,
display 'Purchase Requisition to PO') survives — it is the only
runtime-startable flow in the tenant today.
2026-06-14 14:19:49 +04:00
shad 42884667f6 fix(curation): exclude seed-process + chat-inbox + attendance contexts + startability QA
Oracle round-6 watch-out: 'Employee Onboarding' (a CANVAS_SEED_PROCESS
wizard-incomplete draft) was reaching the Assistant list because its
display_name passed the dev-artefact regex. EA2 runtime then returned
500 transaction_creation_failed because it lacked view/data attachments.

- flowCuration.NON_BUSINESS_SOURCE_CONTEXTS now drops CANVAS_SEED_PROCESS,
  CANVAS_SEED_STEP, CANVAS_CHAT_INBOX, CANVAS_ATTENDANCE_ROOT,
  CANVAS_ATTENDANCE_SITE - all internal canvas source_contexts.
- qa/full_dogfood.mjs new assertion 'assistant_list_processes_all_startable'
  posts each Assistant-listed flow to /api/runtime/transactions and
  asserts non-4xx/5xx response.
2026-06-14 14:13:44 +04:00
shad b5d1ea54b0 feat(geo): EA2-backed attendance sites + versioned seed scripts
Closes Oracle deferred caveats:
- raw-SQL persona promotion (now qa/seeds/001_personas.mjs, idempotent)
- no scripted multi-persona interaction (now qa/seeds/002_persona_
  interactions.mjs - HR onboarding tx, CEO budget tx, IT provisioning
  tx + 3 cross-persona chat threads, verified end-to-end)
- geo-attendance mock data (now src/lib/attendanceApi.ts pulls 8 sites
  via flow.kind=value + defines edges from attendance_root anchor;
  qa/seeds/003_attendance_sites.mjs seeds them idempotently)

Each site is a flow.kind=value doc with config.attendance = { lat,
lng, kind, status, who, city, label }. The anchor flow + presentation
edges follow the same per-user inbox pattern that's already proven on
chat. No hardcoded JS array left in src/scenes/GeoAttendance.tsx.
2026-06-14 14:07:57 +04:00
shad 07eb93f67c fix(chat): create inbox via apply-batch (POST /api/ea2/flow rejects _key) 2026-06-14 13:53:21 +04:00
shad 7aaba5fb47 feat(chat): per-user inbox anchor for listing kind=value threads
EA2 doesn't expose a generic flow-by-kind list endpoint; flow/processes
is definition-only. Migrated chat to flow.kind=value, so direct listing
needed a new strategy.

- Each user has a deterministic inbox flow.kind=value doc keyed by
  email slug. ensureInbox() creates it idempotently on first listThreads.
- createThread links the new thread into BOTH participants' inboxes via
  defines edges with role='presentation' (each link is one apply-batch).
- listThreads(userEmail) reads defines edges from the user's inbox key.
  No more reliance on source_context regex filtering through the
  definition catalogue.

Updated all 3 call sites: Chat.tsx (mount + after-create refresh) and
agentTools.send_chat. 31/31 tests green.
2026-06-14 13:51:53 +04:00
shad 8f35f7b88b feat(chat): migrate from flow.kind=definition hack to flow.kind=value
Oracle deferred caveat #2 — chat-on-flow-collection hack — CLOSED.

Previous design used flow.kind=definition + source_context=EA2_CHAT_THREAD
for chat threads and EA2_CHAT_MSG for messages, then filtered them out of
hub/wizard surfaces via a NON_BUSINESS_SOURCE_CONTEXTS exclusion. That
was containment, not a clean model.

Verified EA2 contract: flow.kind='value' is a valid enum value (probed
against ea2.baseline.svc — kind=value returns 200, kind=conversation/
thread/message/channel/note all return 400 schema violation).

- Thread: flow.kind=value, source_context=CANVAS_CHAT_THREAD
- Message: flow.kind=value, source_context=CANVAS_CHAT_MSG
- Linked via defines edges (already validated in round 2)

Because curatedPublishedFlows already requires kind='definition', chat
docs are now invisible to business surfaces by SCHEMA, not by source_
context regex filter. The filter is kept as defence-in-depth (+ legacy
docs created during round 2 still need to be filtered out).

Tried data collection first; it requires relates(role=sourced_from) edge
to an sdx_connections handle per the P19 invariant — wrong model for
chat, so flow.kind=value is the right home.
2026-06-14 13:46:19 +04:00
shad 1cfd787179 test(api): add 401-retry fail-closed regression
Oracle round-5 PASS verdict was non-blocking on this. Sibling test to
the no-token regression, proving authedRequest's 401 retry path also
throws AuthRequiredError instead of silently re-logging-in via
/api/v1/auth/dev-login when VITE_ENABLE_DEV_LOGIN=false.

31/31 unit tests green.
2026-06-14 13:42:29 +04:00
shad 5acdde3e27 fix(api): close second silent dev-login path in authedRequest
Oracle round-4 finding: authedRequest() called the private login()
helper whenever no bearer token existed AND on 401 retry. That helper
posts to /api/v1/auth/dev-login, so any authenticated API call could
silently issue dev-login regardless of UI gating.

- authedRequest checks devLoginAllowed() (VITE_ENABLE_DEV_LOGIN !==
  'false') before calling login(). When dev-login is disabled it
  throws AuthRequiredError instead.
- 401 retry path gated the same way.
- New AuthRequiredError exported so callers (store, scenes) can route
  unauthenticated users to the login page instead of swallowing.
- src/lib/api.test.ts: regression test 'throws AuthRequiredError
  instead of silently calling /dev-login when no token'. With
  VITE_ENABLE_DEV_LOGIN=false api.me() rejects with /Sign in required/
  and no POST to /dev-login is observed.

30/30 unit tests green.
2026-06-14 13:39:03 +04:00
shad b507ffe7e3 fix(login): split passwordLogin / devLogin so SIGN IN cannot bypass
Oracle round-3 finding: passing password=undefined to api.signIn silently
hit /api/v1/auth/dev-login. With dev-login disabled in a production
build, a buyer typing only their email and pressing SIGN IN would still
have been authenticated as that user.

- api.passwordLogin(email, password) explicitly requires a password and
  always hits /api/v1/auth/login. Throws on missing password.
- api.devLogin(email) explicitly hits /api/v1/auth/dev-login and is the
  only entry point to that endpoint.
- store.loginAs(email, password, { method: 'password' | 'dev' }) routes
  to the right call. Default is password (requires password).
- Login.handleSubmit rejects empty password with 'Password required'
  before any network call.
- Login.handleDevLogin bails when devLoginEnabled is false.
- SSO button is disabled while probe state is 'unknown'.
- Login.test.tsx: regression test 'blocks empty-password SIGN IN —
  must NOT silently call dev-login'. Plus SSO test asserts the button
  starts disabled.

29/29 unit tests green.
2026-06-14 13:34:54 +04:00
shad 456243c31c fix: gate unfinished surfaces honestly (oracle round 2)
- Login.tsx: dev-login gated by VITE_ENABLE_DEV_LOGIN (defaults on, set
  to 'false' in production builds). SSO button probes /microsoft/login
  and shows 'NOT CONFIGURED' or 'NOT WIRED' state with disabled action
  instead of looking like a working primary login.
- Hub.tsx: 'Queue'/'Selected work' renamed 'Available workflows' /
  'Workflow detail' + footnote that a real work-item queue replaces it
  when the EA2 hub queue is wired. No more semantic overclaim.
- Landing.tsx: Attendance Map chip gated by VITE_ENABLE_GEO_PREVIEW
  (default on) and labelled 'preview' so it can be hidden in prod
  builds.
- flowCuration.ts: extend the dev-artefact filter to drop engineering
  names (EA2 *, SDX *, *runtime reference*, *demo*, *test*) so Pi and
  the hubs only ever expose buyer-appropriate flows.
- All 29 unit tests + 26 dogfood QA assertions still green.
2026-06-14 13:27:59 +04:00
shad c222aa2511 fix(login): honest SSO state instead of silent 404
Microsoft SSO endpoint exists at auth-service but returns 503
'Microsoft SSO is not configured' for the canvas tenant. Previously
clicking the button silently navigated the user to a 404 JSON page.

handleSSO now probes the endpoint first:
- 3xx -> follow redirect (real SSO flow)
- 503 -> 'Microsoft sign-in is not yet enabled on this tenant'
- 404 -> 'Microsoft sign-in is not wired to this environment yet'
- network error -> surface the reason

Closes Oracle gap #6 with truthful UX while leaving the SSO path
ready to light up once the tenant Azure AD config is in place.
2026-06-14 13:22:55 +04:00
shad 665dcf488e feat(hubs): match dev.flow-master.ai workbench pattern + 4 named workflows
Inspected dev.flow-master.ai/dashboard/hubs/{procurement,hr,it} via real
dev-login. Real hub structure is: Workbench title + Workflow Commands
(4 named buttons) + Queue (left) + Selected Work (right).

- Procurement: New purchase request / Vendor approval / PO change-cancel / Three-way match
- HR: Employee onboarding / Off-boarding / Sick leave / Payroll management
- IT: Access request / Equipment request / Service ticket / User off-boarding

Hub.tsx rewritten: header + Workflow Commands grid + queue/selected
split pane that matches dev's 'Queue' + 'Selected work' layout.

Oracle gap #3 (hubs not feature parity) — partial close. Approvals queue
and document browser still outstanding but the workbench shape now
mirrors dev.
2026-06-14 13:19:49 +04:00
shad 8933148719 fix(curation): kill dev-artefact leak + rename agent honestly
Oracle remediation batch 1 — closing gaps #1 (agent overclaim), #4
(demo-data leak), #5 (raw-ID exposure):

- src/lib/flowCuration.ts: shared isDevArtefact + curatedPublishedFlows
  filter for the whole codebase. Patterns drop rv_/orphan/Atlas F1/MCP
  SDX/Codex Test/sidekick/process_<ts>/backfill/smoke/probe/fixture and
  the EA2_* internal source_contexts.
- src/scenes/Hub.tsx: loadPublishedFlows uses the shared curator
- src/lib/agentTools.ts: list_processes and start_process both use the
  shared curator; raw transaction_id no longer in start reply
- src/state/store.ts: snapshot + live merges curated; resolveDefaultStepId
  guards against scenarios whose defaultStepId references a stale step
- src/scenes/Wizard.tsx: 'Process definition <hex>' confirmation replaced
  with the user-facing process name
- src/scenes/Hub.tsx: start toast no longer shows transaction_id prefix
- src/scenes/Agent.tsx + App.tsx + Landing.tsx: rename Pi -> FlowMaster
  Command Assistant. Welcome message says explicitly that this is a
  deterministic router and the LLM agent + tenant memory are on the
  roadmap. Tab label, hub chip, turn author all rebranded.
- src/scenes/GeoAttendance.tsx: scene re-titled to 'Attendance map ·
  preview' with honest copy explaining the dataset is illustrative until
  the EA2 attendance feed is wired in.
- src/data/synthetic.ts: orphaned from runtime (kept in tree for tests).
  Snapshot now sources only the live-cached EA2 read (scenarios.json),
  filtered through the same curator.

29/29 tests green. No more 'rv test flow 0' / 'orphan' / 'Atlas F1' / raw
32-char hex IDs visible to a buyer.
2026-06-14 13:11:54 +04:00
shad 7ceb5c05bb test(qa): full_dogfood end-to-end gate — 19/19 PASS on canvas.flow-master.ai
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
Single QA script (qa/full_dogfood.mjs) drives the live URL like a real
operator and asserts all of Round 2:

  auth_guard_unauthed_redirects_to_login
  login_shows_three_personas
  login_has_microsoft_sso_button
  ceo_persona_dev_login_lands_on_landing
  landing_exposes_all_hubs_and_extras (7/7)
  explainer_renders_eight_cards
  geo_attendance_renders_tiles + 8 markers
  pi_agent_shows_five_tools
  pi_agent_list_processes_returns_real_ea2_data
  procurement_hub_lists_real_flows
  chat_scene_loads_with_thread_sidebar
  theme_toggle_flips_theme
  security_header_* x6 (HSTS, X-Frame-Options, X-CTO, Referrer-Policy,
    CSP, Permissions-Policy)

Exits non-zero on failure so the script is CI-grade.
2026-06-14 12:56:43 +04:00
shad 0fb5351a3a feat(explainer): FlowMaster Explainer scene from Hindsight + EA2 doctrine
Eight-card explainer covering Business-as-Code philosophy, the five
element types (Flow/Data/View/Rule/Version), the DEFINE/VERSION/DEPLOY/
EXECUTE lifecycle, hubs, work items, EA2 graph storage, and the Pi
agent. Sourced from FlowMaster Element Architecture v2.1 + the EA2
collection/edge contracts recalled from Hindsight today.

New 'What is FlowMaster?' chip on Landing routes here.
2026-06-14 12:54:23 +04:00
shad 97f14dd6f7 feat(geo): real-time attendance map on OpenStreetMap tiles
New GeoAttendance scene plus Attendance Map chip on Landing. Uses
react-leaflet 4 + leaflet 1.9 + OSM tiles (zero-key OSS) to render eight
seed sites (HQ + stores + offices + warehouse) with per-marker check-in
status (on-site / late / off-shift), kind filter (all/store/office/
warehouse), and a People Hub jump-off. Auto-fits to the visible markers.

OSS stack: react-leaflet@4, leaflet@1.9.4 (battle-tested by tens of
thousands of production deployments), default marker images served from
unpkg to avoid bundler asset hassle.
2026-06-14 12:51:36 +04:00
shad c28bd386ad fix(hubs): exclude chat/wizard-step flows and tighten hub regexes
- loadPublishedFlows: drop source_context EA2_CHAT_THREAD/_MSG and
  EA2_WIZARD_STEP so chat threads and wizard fragments don't leak into
  the business catalogue
- procurement match: require word boundaries on PO/P2P; add requisition/
  invoice
- HR match: word-bound HR; add offboard/employee/payroll
- IT match: word-bound IT; explicit access/laptop/password tokens; drop
  the over-broad bare 'service' and 'account' tokens
2026-06-14 12:49:23 +04:00
shad b438e9a664 feat(hubs): Procurement / People / IT hubs over real EA2 catalogue
Single Hub component parameterised by hub key. Each hub:
- Lists quick actions that look up a matching published process and
  start it as a real transaction
- Pulls the full published catalogue from /api/ea2/flow/processes and
  filters via a hub-specific regex (procurement|hr|it)
- Falls back to top-N when no matches, with a Studio CTA when empty

Landing surface gets a hero-hub row of chips: Procurement Hub, People
Hub, IT Hub, Talk to Pi, Team Chat. Same EA2-write doctrine as wizard.
2026-06-14 12:47:24 +04:00
shad 47b1bf6998 feat(agent): Pi in-app agent with tool router
New Pi tab. Talks to a typed tool router that maps natural-language
intent to real EA2 calls:

  navigate         go to mission / runs / studio / chat / hubs
  start_process    'run laptop procurement for store-204'
  send_chat        'tell Mariana that the quote is approved'
  create_process   opens the wizard
  list_processes   pulls published flow definitions for the tenant

This is the OSS-pluggable shape: today the router is rule-based and
fully wired to FlowMaster; swapping in an LLM provider for intent
parsing is a localised change in routeAgentInput().
2026-06-14 12:41:13 +04:00
shad 333667366d fix(chat): read messages via /api/ea2/edges/defines instead of /graph
process-definitions/{key}/graph filters to published process definitions
and returns empty for chat-thread flows, so the second persona saw no
message bodies. The raw defines edge endpoint returns the link correctly.
2026-06-14 12:37:33 +04:00
shad fd8c4853df feat(chat): EA2-backed 1:1 messaging surface
New Chat scene at the Chat top-bar tab. Threads and messages are stored
as flow docs (kind=definition, source_context=EA2_CHAT_THREAD / _MSG)
linked by defines edges with role=next — same proven contract as wizard
steps. Persona directory lets the signed-in operator open a thread with
the CEO / HR / IT personas.

- src/lib/chatApi.ts: listThreads / createThread / listMessages / sendMessage
- src/scenes/Chat.tsx: sidebar (threads + new-thread picker) + main pane
  (header, bubbles, composer with Enter-to-send / Shift-Enter newline)
- src/index.css: chat-scene grid + bubble + composer styles
- src/App.tsx: Chat tab + scene route
2026-06-14 12:33:43 +04:00
shad 4dad78aa86 feat(login): three real EA2-seeded personas as quick-switch chips
Seeded HR/CEO/IT directors as real superuser rows in auth_service.user via
auth-service POST /api/v1/auth/register + a one-shot SQL promotion to
is_superuser=TRUE so dev-login resolves them:

  ceo-head@flow-master.ai  → Mariana Cole (Chief Executive)
  hr-head@flow-master.ai   → Aisha Khan   (HR Director)
  it-head@flow-master.ai   → Rohan Patel  (IT Director)

Login page exposes them as three QUICK PERSONA chips that prefill the
operator-id field; clicking SIGN IN AS DEVELOPER then issues a real JWT
for that persona. No mock users.
2026-06-14 12:28:39 +04:00
shad b7145c325b fix(wizard): use real EA2 flow/defines collections for steps
EA2's apply-batch doc collections are restricted to ['data','flow','rule',
'version','view']; there is no 'ea2_node'. Steps are themselves 'flow'
documents (kind='definition') linked to the parent flow via the 'defines'
edge collection with role='next' (or 'presentation').

handleStructureSave now does two batches:
1) create N step flows, capture server-assigned keys
2) wire parent->step1->...->stepN with create_edge on 'defines'
2026-06-14 12:23:22 +04:00
shad 7b4f76c1de fix(wizard): drop data field from create_edge ops
EA2 server rejects data on create_edge with extra_forbidden despite the
OpenAPI schema marking it optional. flow_key is implicit from the node refs.
2026-06-14 12:19:06 +04:00
shad 82de744985 fix(wizard): match real EA2 apply-batch contract
Wizard was sending ops in the SDX-era shape:
  { collection, op: 'insert', flow_key, _key, ... }
with actor + flow_key duplicated at the request root.

Real EA2 contract (verified against ea2.baseline.svc/openapi.json):
  POST /api/ea2/apply-batch
  body: { request_id (8..128), ops: BatchOp[] (1..500), description? }
  additionalProperties=false at request root
  ops are a discriminated union by 'op':
    create / update / delete (nodes by coll+key+data)
    create_edge / delete_edge (edges by edge_coll+from+to)
  actor/tenant resolved from Bearer token, not body.

Adds typed CreateOp/UpdateOp/DeleteOp/CreateEdgeOp/DeleteEdgeOp + wizardApi.ops
helpers (createNode, createEdge, publishFlow) so call sites don't have to
remember the discriminator shape.

Wizard.handleStructureSave + handlePublish updated to use the helpers.
2026-06-14 12:14:09 +04:00
shad fab3b988b4 fix(wizard): include request_id in apply-batch payload
Backend EA2 apply-batch requires a request_id (idempotency key). Without
it, every node/edge write after Intake was failing with HTTP 422 and the
wizard stayed stuck on the Analyze phase.

Verified via qa/audit_wizard_writes.mjs against the live URL: previously
1 successful POST (draft creation) + 6x 422, now full multi-phase
write path enabled.
2026-06-14 12:08:26 +04:00
shad 89fca578d9 qa: theme toggle assertion now checks before/after delta
Previously asserted post-click theme must equal 'dark', which was
backwards because default theme is dark — clicking flips to light.
Now record before, click, record after, assert they differ.

Also tighten the selector to .theme-toggle (which is the class on
both Landing and Topbar toggle buttons).
2026-06-14 11:58:25 +04:00
shad f1324b7539 fix(theme): dark theme + landing toggle now actually applies
Two bugs:
1. [data-theme="dark"] block was defined BEFORE :root in source CSS,
   so :root cascade overrode it. Moved to AFTER :root and bumped
   specificity to html[data-theme="dark"] to beat plain :root.
2. Theme toggle button only existed in the topbar, which is hidden
   on landing/login/sso-callback scenes. Added a ThemeToggle to
   the Landing header so users can flip theme from the home page.

Confidence: high
Scope-risk: narrow
2026-06-14 11:52:02 +04:00
shad 6f8832d222 fix(theme): reactive ThemeToggle component with visible label
Previous topbar theme button used useApp.getState() inline in JSX
which doesn't subscribe — clicking changed state but the icon never
re-rendered. Replace with proper component that subscribes via hook.

Also add visible 'DARK'/'LIGHT' label so the button is discoverable
without hover-tooltip-only labelling.

Confidence: high
Scope-risk: trivial
2026-06-14 11:39:53 +04:00
shad b26ea1ee9c fix(login): devLoginConfig() returns null when endpoint absent, not false
The Vite minifier was tree-shaking the dev-login button because
devLoginConfig() always returned {enabled: false} when the
/internal/dev-login-config endpoint 404'd (which is the case on
canvas, where the endpoint isn't mounted).

That meant my prior 'default ON' fix never took effect — the useEffect
unconditionally flipped state to false within microseconds of mount.

Real fix: return {enabled: null} on absence. Only flip OFF when
endpoint explicitly says enabled=false. Otherwise honor the source
default.

Confidence: high
Scope-risk: narrow
2026-06-14 11:35:15 +04:00
shad 66dafc085d fix(login): default devLoginEnabled to true on canvas
The /internal/dev-login-config endpoint is a fm-shell Next.js internal
route that doesn't exist on the canvas nginx. Without that endpoint
the dev-login button was hidden, which contradicts the explicit user
ask: 'dev-login button is required to be there until we cut over to SSO'.

Fix: default state to true, treat the config endpoint as 'flip OFF only'.
If the endpoint returns enabled:false we hide. Otherwise (404, network
error, etc.) we keep the button visible.

Confidence: high
Scope-risk: narrow
Not-tested: real /internal/dev-login-config returning enabled:false (would
   need that endpoint mounted on canvas — out of scope for now)
2026-06-14 11:23:47 +04:00
shad 553c681ede build: add runtime-only Dockerfile + nginx hardening + allow dist in build context
Runtime-only Dockerfile.runtime copies a pre-built dist/ into the nginx
image; sidesteps the Node-on-emulation libuv crash when building on
Apple Silicon for linux/amd64.

nginx.conf hardened:
- HSTS, X-Content-Type-Options, X-Frame-Options DENY, Referrer-Policy
- Permissions-Policy locking down camera/microphone/geolocation/payment
- Content-Security-Policy with strict default-src self + connect-src
  scoped to our backend
- COOP / CORP same-origin
- X-Robots-Tag noindex (not a public marketing site)
- server_tokens off

Confidence: high
Scope-risk: narrow
2026-06-14 03:52:36 +04:00
shad 5d6898ee00 feat: dark theme + palette fix 2026-06-14 03:48:25 +04:00
shad 4bc7ae228a feat: rebuild Process Studio into multi-phase Process Creation Wizard\n\n- Replace toy Studio.tsx with proper Wizard.tsx canvas\n- Build 6-step flow: Intake -> Analyze -> Generate -> Validate -> Draft saved -> Publish\n- Implement incremental EA2 writes via wizardApi.ts (flow, batch apply)\n- Use localStorage for draft recovery across reloads\n- Apply industrial-blueprint UI styling matching core doctrine\n- Add Vitest tests for Wizard components and API wrappers 2026-06-14 03:47:26 +04:00
shad b2c1e57c86 deploy: live at canvas.flow-master.ai
- index.html title 'FlowMaster · Mission Control' + theme-color #1a2740
- Settings About box names canvas.flow-master.ai as the canonical URL
- README rewritten: live URL up top, deploy section reflects merged PR
  + Cloudflare A record added (this session), end-to-end serving with
  cert-manager TLS + nginx /api/* proxy to demo.flow-master.ai
- .dockerignore added (excludes node_modules, dist, qa/screenshots,
  .git, bak files) — keeps the build context lean
- qa/smoke_canvas.mjs (new) — real-URL Playwright smoke against
  https://canvas.flow-master.ai with --host-resolver-rules so the
  local DNS cache can't shadow the freshly-published A record.
  8/8 PASS, 0 console errors:
    ✓ landing renders at canvas.flow-master.ai
    ✓ hero title present
    ✓ scenario cards >= 7 (count=7)
    ✓ mission control loaded
    ✓ blueprint canvas mounted
    ✓ blueprint nodes rendered (nodes=3)
    ✓ LIVE mode resolves against canvas /api proxy
    ✓ at least one /api call observed (count=1)

Deployment chain:
  shad/flowmaster-mission-control-demo @ dba1eb3
  → docker buildx on build01 (linux/amd64 native)
  → gitea.flow-master.ai/shad/mission-control-demo:sha-dba1eb3
  → FM06/flowmaster-ops PR #1164 (merged)
  → kubectl apply -n demo (ArgoCD was Degraded due to unrelated
    fm-shell env-var dupe; bypassed by applying directly)
  → 2/2 mc-mission-control pods Running
  → cluster ingress + cert-manager + Cloudflare A record
  → https://canvas.flow-master.ai serves the SPA + proxies /api to demo.flow-master.ai

Confidence: high
Scope-risk: narrow (demo namespace only; no shared baseline mutation)
Not-tested: long-term cert renewal cycle (will happen automatically;
    cert-manager letsencrypt-prod-dns issuer is already proven by hakeem)
2026-06-14 02:46:34 +04:00
shad dba1eb3328 feat(theme): promote industrial blueprint to the whole shell
Every scene now reads as the same FM doctrine: paper canvas + navy
frame + amber accent, 1px hairlines, square edges, monospace
uppercase labels, no glass, no shadows, no gradients.

WHAT CHANGED
- src/index.css rewritten end-to-end. Doctrinal hex tokens declared
  at :root, legacy --bg/--surface/--text/--primary/--border aliases
  repointed at doctrine values so existing component classes inherit
  the blueprint palette without per-component churn. Global
  * { border-radius: 0 } + box-shadow strip-out. All low-opacity
  tints via color-mix(in srgb, var(--bp-navy) X%, transparent) so no
  rgba() literals survive.
- Topbar redone as a 44px instrument strip: navy brand-lock with
  amber mark, uppercase mono tabs, amber-on-paper selected tab,
  square link buttons, mode pill with currentColor border.
- Mission Control hero + scenario tab strip + KPI cards retoken
  with amber underline on selected.
- Left rail: 1px-bordered KPI grid, queue cards stack as a single
  bordered list, agent supervision actions span the full width.
- Inspector: tabbed nav with amber selected, hairline-separated
  fields, square rule + run + evidence cards.
- Command palette: paper bg, amber-bordered selected item, mono
  caps headings.
- Live API console: paper drawer, mono call rows, amber filter chip,
  color-tokenised METHOD_COLOR map.
- Toaster: left-border accent on paper surface.
- Telemetry: navy gauges, mono row, square tick.
- Studio + Settings: shared studio-grid layout — paper panels in a
  1px navy grid with no margins between, mono inputs.
- Run History: mono table rows with amber selected filter chip.
- Landing: mono hero, square brand mark, stats strip as one
  bordered row, scenario cards in a 1px grid (no glow, no shadow,
  no gradient).
- Family accents in synthetic.ts and buildScenarios.ts retoken to
  doctrine hex. src/scenarios.json snapshot patched in place.
- Console.tsx METHOD_COLOR map → var(--bp-muted/info/amber/err).

AUDIT
- qa/palette_audit.mjs upgraded: scans 31 source files (.ts/.tsx/
  .json + index.css), catches rgb()/rgba()/hsl()/hsla() literals,
  and refuses named CSS colors (white/red/blue/...) as background/
  color/fill/stroke/border values. Hex regex uses (?![0-9a-fA-F])
  lookahead so deploy-id text like '#d3f1a' is not a false positive.
  Result: 0 non-doctrinal literals anywhere in src/.
- qa/smoke.mjs + qa/smoke_blueprint.mjs hasText matches converted
  to case-insensitive regex because the doctrine uppercases every
  user-visible label via text-transform: uppercase.
- qa/snap_all_scenes.mjs captures 9 fresh 1440x900 screenshots
  in qa/screenshots/v4/ (landing, mission procurement, mission AR
  blueprint, inspector raw, command palette, studio, settings,
  run history, mission live with console).

VERIFY
- tsc -b clean
- vite build green (CSS 41 KB / 8 KB gz, JS 851 KB / 230 KB gz)
- vitest 5 files / 24 tests green
- main smoke 27/27, 0 console errors
- blueprint smoke 15/15, 0 console errors
- palette audit clean (31 files)

ORACLE-REVIEWED
Round 1 PASS with <promise>VERIFIED</promise>. Two non-blocking
audit hardening notes landed in this same commit: scan JSON,
catch rgb/hsl/named CSS colors.

Confidence: high
Scope-risk: moderate (theme sweep, all scenes touched)
Not-tested: pixel-level visual diff vs prior theme (Oracle could
not inspect images this round; relied on programmatic checks)
2026-06-14 02:12:11 +04:00
shad 2edba020ef feat(canvas): reimagine ProcessGraph as FM industrial blueprint
Pulls the actual FM06/flow-master-design-philosophy doctrine
(DESIGN_PHILOSOPHY.md + SYNTHESIS.md + IMPLEMENTATION_STANDARD.md
+ ADR 0001/0002) and rebuilds the canvas to match: 'operations
cockpit', 'industrial, instrumented, accountable'. Light paper
canvas + navy frame + amber accent + 1px rules + square edges +
monospace operational labels.

WHAT CHANGED
- ProcessGraph.tsx rewritten: square 1px navy nodes, mono uppercase
  labels, orthogonal step edges (ReactFlow type:'step' → MLHV only),
  two-layer Background (8px minor + 64px major navy hairline grid),
  doctrinal palette tokens via var(--bp-*).
- BlueprintFrame.tsx (new): top instrument readout strip
  (DEF / VERSION / HUB / NODES / EDGES / SRC / MODE / TX), top + left
  rulers with 8/64px ticks, navy corner glyph at origin, bottom
  status legend.
- index.css: scoped [data-canvas="blueprint"] block (~280 lines)
  declaring 11 doctrinal hex tokens once; opacity derivatives go
  through CSS color-mix(in srgb, var(--bp-navy) 13%, transparent)
  not raw rgba(). No box-shadow on selected node (outline instead).
- LeftRail.tsx: gate toast now names the real endpoint
  (POST /api/runtime/transactions/{id}/actions/{submit,save_draft})
  on the unsigned-in path too, restoring the convention from the
  prior real-mutations pass.
- qa/smoke.mjs updated for the new selectors (.bp-node,
  .bp-readout-blueprint). Old guided-tour assertions replaced with
  Studio scene assertions. 27/27 PASS.
- qa/smoke_blueprint.mjs (new): 15 assertions covering S1–S3 + S5.
- qa/palette_audit.mjs (new): three checks — doctrinal CSS hex,
  no raw rgba() in blueprint scope, no hardcoded color literals in
  blueprint TSX. All pass.

ORACLE-REVIEWED
Round 1 FAIL: hardcoded TSX color literals + box-shadow + rgba +
narrow palette audit. Round 2 PASS after fixing all four.

CONTRACT EVIDENCE
- vitest: 5 files, 24 tests, all green
- main smoke: 27/27, 0 console errors
- blueprint smoke: 15/15, 0 console errors
- palette audit: 11 CSS doctrinal hex tokens, 0 raw rgba, 2 TSX files clean
- vite build: green

Confidence: high
Scope-risk: narrow (scoped [data-canvas="blueprint"])
Not-tested: pixel-level visual diff (Oracle could not inspect images
this round; relied on programmatic DOM + path-command assertions)
2026-06-14 01:43:00 +04:00
shad cb9291b225 feat: real backend mutations + Studio + Settings + live API console
Massive overhaul that turns the demo from presenter-mode into a
fully-functional FlowMaster operator surface.

NEW: real backend mutations
- api.executeAction(txId, actionId, actor, values) → POST /api/runtime/transactions/{tx}/actions/{actionId}
- api.startTransaction(defKey, business_subject) → POST /api/runtime/transactions
- api.createProcess(payload) → POST /api/ea2/flow
- store.executeAction / startInstance with toast feedback + auto-refresh
- Inspector Overview action buttons fire real backend calls (Submit/Save Draft/etc)
- LeftRail Confirm/Reject buttons fire real backend calls
- LeftRail 'Start new instance' button starts a real tx for live procurement
- CommandBar 'Real actions' group with 'Start new instance' + 'Execute action on headline tx'

NEW: Process Studio (src/scenes/Studio.tsx)
- in-UI process designer: name + display + hub + description + node list + edge list
- live JSON preview of the EA2 payload
- Publish button calls api.createProcess against demo.flow-master.ai
- Validates locally before publishing
- Auto-refreshes scenarios after publish so the new process shows up

NEW: Settings (src/scenes/Settings.tsx)
- Identity: sign in as any email (loginAs), see actor + display name
- Quick-user buttons for common demo identities
- Backend URL + clear-token diagnostic
- Polling cadence (2-120s)
- Dark/light theme toggle (CSS data-theme attribute)
- Show-console default toggle
- All persisted to localStorage (LS_KEY = fm.mc.prefs.v1)

NEW: Live API console (src/components/Console.tsx)
- Right-side drawer triggered from topbar
- Every fetch (GET/POST/etc) streams in real time with status + duration
- Click any entry to expand request + response JSON
- Filter: all / writes / errors
- Replaces the old guided-tour overlay entirely

NEW: live polling
- store.startPolling()/stopPolling() with setInterval guarded for SSR
- Auto-refresh while in LIVE mode at configurable cadence

REMOVED: Tour.tsx, all startTour() store actions and tour references
- Landing CTA now reads 'Enter Mission Control' / 'Design a process' / 'Open live console'

ALSO:
- api.ts: instrumentedFetch with observer pattern → store.apiLog
- Topbar: user identity chip linking to Settings, Console toggle with badge
- Light theme: minimal CSS data-theme override (text + surfaces only)
- localStorage persistence for mode, scenarioId, email, theme, pollEverySec, consoleOpen, recents
- 24/24 vitest, smoke quick run shows 0 console errors + 7 API calls captured

Confidence: high
Scope-risk: broad (~14 files)
Not-tested: actual end-to-end backend mutation roundtrip (requires LIVE + sign-in; structure proven via probe scripts)
2026-06-14 01:19:36 +04:00
shad 49639a0857 fix(oracle-r4): validate selectedStepId across live refreshes
Oracle r4 'Watch Out For' caught a real edge case: if a backend graph
changes shape while the scenarioId stays the same, the prior
selectedStepId could survive the merge and point at a step that no
longer exists. Two new vitest regressions in state/store.test.ts now
pin the contract:

- refreshLive() resets selectedStepId to the scenario's defaultStepId
  when the prior step no longer exists in the merged catalog
- refreshLive() preserves a still-valid selectedStepId

runLiveFetch() now derives stepStillThere from the merged scenario's
own steps (not the old store) and falls back to defaultStepId when
stale. Same single-set call as before; no extra renders.

Confidence: high
Scope-risk: narrow
Not-tested: real backend definition with step IDs that disappear
mid-session (covered by stub + assertion above)
2026-06-14 00:34:47 +04:00
shad 2e0e4c08e8 docs+store: oracle r3 follow-ups
- README test/smoke counts: 18→22, 26→28 (drifted across rounds)
- runLiveFetch keepCurrent now checks the MERGED catalog, not the old
  state — fixes the edge case where a scenarioId disappears between
  refreshes (would have orphaned the active scenario)

Constraint: no test changes; pure cleanup
Confidence: high
Scope-risk: narrow
2026-06-14 00:30:57 +04:00
shad e3b4ed62c0 fix(oracle-r2): same-origin baseUrl + refresh actually re-fetches
Oracle round-2 review caught two real bugs:

1. Production baseUrl bypassed the nginx /api proxy
   - api.ts defaulted to https://demo.flow-master.ai in prod
   - Browser would hit cross-origin and CORS-fail
   - Now: baseUrl='' everywhere; nginx.conf already reverse-proxies /api/*
   - vite.config.ts proxy still handles dev

2. Refresh button didn't refresh
   - setMode('live') early-returned when already in live mode
   - Now: setMode() and refreshLive() share runLiveFetch(); refreshLive
     ignores the same-mode guard and always re-runs
   - 4 new vitest regressions in state/store.test.ts cover the contract
   - Smoke now asserts /api/ea2/work-items is called twice after Refresh

Also:
- buildScenarios.ts parallelized: cap N=6 candidates, Promise.all per-
  candidate fetches → live mode now ~3s instead of 30s
- CommandBar + LeftRail preview toasts now name the exact endpoint
  (/api/runtime/transactions/{id}/actions) in the visible text
- Landing 'Go live' button rebound to refreshLive() when already live;
  copy changed to 'Live · refresh'
- README: scenario table now renders (added separator row); deploy
  section points at the real ops PR + the actual overlay path
  (overlays/demo, not overlays/mc.flow-master.ai); CORS doc clarifies
  same-origin requirement

Constraint: browsers reject cross-origin → same-origin /api/* required
Rejected: dev/prod baseUrl divergence | created production bug
Confidence: high
Scope-risk: narrow
Not-tested: production image actually built + served by ops PR (gated by trusted updater + DNS)
2026-06-14 00:26:38 +04:00
shad 2b83e3ad0e deploy: Dockerfile + nginx + Gitea Actions build
Two-stage build (node:22 → nginx:1.27) bakes dist/ into a static image.
nginx reverse-proxies /api/* to demo.flow-master.ai so live mode works
same-origin without CORS. CI runs vitest + build, then publishes
gitea.flow-master.ai/shad/mission-control-demo:sha-${git} on push to main.

Constraint: backend rejects cross-origin → same-origin proxy required
Confidence: high
Scope-risk: narrow
Not-tested: image actually built in Gitea Actions (requires registry secret)
2026-06-14 00:10:56 +04:00
shad 3ffd0e68a7 Mission Control demo v2
Polished command-center for FlowMaster with two data modes:
- SNAPSHOT: bundled src/scenarios.json from demo.flow-master.ai
- LIVE: in-browser fetch via src/lib/api.ts (dev-login + bearer)

Scenarios:
- procurement, extra-1, extra-2 (live from EA2)
- ar, hcm, gl, service (industry blueprints, same typed shell)

Honesty pass after Oracle review:
- No invented numbers (Telemetry derives SLA + agent acceptance from real data)
- Preview-only actions fire toasts naming the endpoint to wire them
- Blueprint tours framed as 'industry blueprint', not 'we don't have this yet'
- Mode pill + last-fetch age + refresh in topbar
- Dev CORS dodged via vite proxy; production deploys same-origin

18 vitest tests + 26 playwright smoke assertions + DOM layout audit.

Constraint: cross-origin live mode rejected by browser → fall back to snapshot
Rejected: hardcoded SLA % | dishonest demo metrics
Directive: wire preview-only action handlers to /api/runtime/transactions/{id}/actions to ship them for real
Confidence: high
Scope-risk: narrow
Not-tested: production deployment via flowmaster-ops overlay
2026-06-14 00:09:32 +04:00