From 56d08d6d6e5fa2ad9a356e328f9810e17e0fb21f Mon Sep 17 00:00:00 2001 From: Tomasz Zajac Date: Fri, 25 Sep 2026 10:47:48 +0200 Subject: [PATCH] feat(hub): full per-actor screen flows in every blueprint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first pass gave each blueprint three isolated screens; a mockup set should show how each actor actually moves through the product. Every blueprint now carries one flow per actor — a `group` (" journey") holding its mockups in step order — 79 screens in 19 flows (was 21). - Screens are linked with navigates-to, labelled with the triggering action, including branches (e.g. Payment -> Confirmation or Payment Declined -> retry) and return edges that close loops (Update Payment -> Dashboard, Exception Detail -> Exceptions, Case Resolved -> Queue, ...). Only natural end states have no way forward. - The 21 existing screens are kept under the same ids as flow steps. - New screens get illustrates (to the blueprint's requirements where it fits) and presented-by like the existing ones; descriptions use the blueprint's template params. - Flow groups are stacked below the ADRs, five screens per row. The blueprint mockup test now also requires at least 8 mockups per blueprint, every mockup inside a flow group, and every mockup linked into its flow by navigates-to. Co-Authored-By: Claude Opus 5.5 --- .../bp-ai-assistant-platform.radical | 276 +++++++++++- .../bp-analytical-data-platform.radical | 247 ++++++++++- hub/blueprint/bp-ecommerce-platform.radical | 407 +++++++++++++++++- .../bp-fintech-ledger-platform.radical | 338 ++++++++++++++- hub/blueprint/bp-marketplace-platform.radical | 400 ++++++++++++++++- .../bp-realtime-collaboration.radical | 263 ++++++++++- hub/blueprint/bp-saas-b2b-platform.radical | 342 ++++++++++++++- tests/hubCatalogue.test.ts | 16 +- 8 files changed, 2204 insertions(+), 85 deletions(-) diff --git a/hub/blueprint/bp-ai-assistant-platform.radical b/hub/blueprint/bp-ai-assistant-platform.radical index 0c4a045..a8ac504 100644 --- a/hub/blueprint/bp-ai-assistant-platform.radical +++ b/hub/blueprint/bp-ai-assistant-platform.radical @@ -405,26 +405,92 @@ "consequences": "Knowledge freshness becomes index-refresh latency instead of training-cycle latency, but overall answer quality is now capped by retrieval quality, not just model quality.", "alternatives": "1. Periodic fine-tuning on new knowledge — keeps knowledge \"in\" the model, but the feedback loop is slow and risks catastrophic forgetting of earlier knowledge. 2. Retrieval only (chosen). 3. Retrieval plus light fine-tuning for tone and format only — a reasonable hybrid, at the cost of a second training pipeline. 4. Stuffing all knowledge into the prompt without retrieval — doesn't scale past a small, static document set." }, + { + "id": "flow-customer", + "type": "group", + "label": "Customer journey", + "description": "Getting an answer from the assistant, or a person when it cannot help.", + "x": 0, + "y": 2084, + "width": 1240, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-chat-start", + "type": "mockup", + "label": "Chat — Start", + "description": "Entry point with the most common intents.", + "parentId": "flow-customer", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "chat widget", + "wireframe": "\n \n \n \n \n \n \n \n \n \n \n \n Support\n \n Type a message…\n \n ➤\n \n AI assistant\n Hi! I can help with orders,\n returns and your account.\n \n Where is my order?\n \n Return an item\n \n Talk to a person\n Answers cite their sources.\n" + }, + { + "id": "mockup-chat-answer", + "type": "mockup", + "label": "Chat — Grounded Answer", + "description": "Answer grounded in retrieved sources; actions need the customer's confirmation.", + "parentId": "flow-customer", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "chat widget", + "wireframe": "\n \n \n \n \n \n \n \n \n \n \n \n Support\n \n Type a message…\n \n ➤\n \n AI assistant\n \n \n \n \n \n \n \n \n Order 98213 · Returns policy §2\n \n Start a return for order 98213?\n \n Yes\n \n Not now\n" + }, { "id": "mockup-support-chat", "type": "mockup", - "label": "Support Chat", + "label": "Chat — Handoff", "description": "Customer-facing chat: the assistant answers with cited sources when it can ground the answer and hands off to a person when it cannot.", - "x": 0, - "y": 2084, + "parentId": "flow-customer", + "x": 670, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "chat widget", "wireframe": "\n \n \n \n \n \n \n \n \n \n \n \n Support\n \n AI assistant\n \n \n \n \n \n \n \n \n Source: Returns policy §2\n \n \n \n Connecting you to a person —\n usually within a few minutes.\n \n Type a message…\n \n ➤\n" }, + { + "id": "mockup-chat-human", + "type": "mockup", + "label": "Chat — Agent Joined", + "description": "A person joins with the full case history, within {{ESCALATION_SLA_MIN}} minutes of the handoff.", + "parentId": "flow-customer", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "chat widget", + "wireframe": "\n \n \n \n \n \n \n \n \n \n \n \n Support\n \n Type a message…\n \n ➤\n \n Agent: Maya\n \n \n \n \n Maya joined — picked up in 3 min\n \n \n \n \n \n \n" + }, + { + "id": "flow-human-agent", + "type": "group", + "label": "Human agent journey", + "description": "Picking up escalations and closing them with a reviewed action.", + "x": 0, + "y": 2494, + "width": 1240, + "height": 330, + "collapsed": false + }, { "id": "mockup-escalation-queue", "type": "mockup", "label": "Escalation Queue", "description": "Human agents pick up escalated cases oldest-first; each case must be picked up within {{ESCALATION_SLA_MIN}} minutes.", - "x": 320, - "y": 2084, + "parentId": "flow-human-agent", + "x": 30, + "y": 110, "width": 220, "height": 190, "collapsed": false, @@ -436,13 +502,81 @@ "type": "mockup", "label": "Case Review", "description": "The assistant's proposed action with its confidence, per-field extraction confidence (below {{EXTRACTION_REVIEW_THRESHOLD_PCT}}% needs review) and the sources it used.", - "x": 640, - "y": 2084, + "parentId": "flow-human-agent", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/console/cases/:id", "wireframe": "\n \n \n \n Agent Console — Case #4821\n Back to queue\n \n \n \n \n \n \n \n \n \n \n \n \n \n Assistant proposal\n \n Refund €42.10 to the original card\n Confidence\n 62% — below threshold\n \n \n \n \n Extracted field\n Value\n Conf.\n \n Order id\n 98213\n 98%\n \n Amount\n €42.10\n \n 61%\n \n Order date\n 3 Mar\n 93%\n Sources: Returns policy §2 · Order 98213 · Payment log\n Decision, sources and scores go to the audit trail.\n \n Edit\n \n Approve & send\n" + }, + { + "id": "mockup-case-resolved", + "type": "mockup", + "label": "Case Resolved", + "description": "Resolution with every autonomous and human decision recorded for {{AUDIT_RETENTION_DAYS}} days.", + "parentId": "flow-human-agent", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/console/cases/:id", + "wireframe": "\n \n \n \n Agent Console — Case #4821\n Back to queue\n \n \n Case resolved\n Refund of €42.10 sent · customer notified · 9 min end to end\n Recorded in the case audit trail\n \n When\n Actor\n Event\n \n 10:02\n assistant\n Retrieved 3 sources, confidence 62%\n \n 10:02\n assistant\n Escalated: below auto-approve threshold\n \n 10:05\n Maya\n Picked up case\n \n 10:09\n Maya\n Edited amount, approved refund\n \n Add to eval set\n \n Next case\n" + }, + { + "id": "mockup-case-audit", + "type": "mockup", + "label": "Case Audit Trail", + "description": "Search decisions, sources and confidence across cases for audits.", + "parentId": "flow-human-agent", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/console/audit", + "wireframe": "\n \n \n \n Agent Console\n Audit\n My cases\n Queue\n Case audit trail\n \n Case, customer or agent\n \n 90 days\n \n \n Search\n \n Case\n Decision\n Conf.\n Sources\n By\n \n #4821\n Refund €42.10\n 62%\n 3\n Maya\n \n #4820\n Answered return question\n 94%\n 2\n assistant\n \n #4819\n Escalated — legal\n —\n 1\n assistant\n \n #4818\n \n \n \n \n Kept for 365 days; exportable for audits.\n \n Export\n" + }, + { + "id": "flow-assistant-ops", + "type": "group", + "label": "Assistant ops journey", + "description": "Keeping knowledge fresh and answer quality from drifting.", + "x": 0, + "y": 2904, + "width": 600, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-kb-sources", + "type": "mockup", + "label": "Knowledge Sources", + "description": "Source sync status against the {{KB_STALENESS_HOURS}}-hour freshness limit.", + "parentId": "flow-assistant-ops", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/ops/knowledge", + "wireframe": "\n \n \n \n Assistant Ops\n Audit\n Quality\n Knowledge\n Knowledge sources\n \n + Add source\n \n Source\n Documents\n Last sync\n Status\n \n Help centre\n 412\n 12 min ago\n \n fresh\n \n Returns policy\n 18\n 1 h ago\n \n fresh\n \n \n Product catalogue\n 9,210\n 6 h ago\n \n stale > 4 h\n \n Internal runbooks\n 77\n 2 h ago\n \n fresh\n Stale sources are down-ranked in retrieval until they sync again.\n \n Sync now\n" + }, + { + "id": "mockup-quality-drift", + "type": "mockup", + "label": "Quality & Drift", + "description": "Autonomous resolution rate, eval results and cost per case.", + "parentId": "flow-assistant-ops", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/ops/quality", + "wireframe": "\n \n \n \n Assistant Ops\n Audit\n Quality\n Knowledge\n Quality & drift\n \n Resolved autonomously\n 64%\n \n Eval pass rate\n 91%\n \n Cost / case\n €0.04\n \n Eval pass rate by release\n \n \n Release gate: blocks deploys that drop the eval pass rate.\n \n Open eval set\n" } ], "relations": [ @@ -611,6 +745,38 @@ "targetId": "adr-confidence-gated-autonomy", "relationType": "traces-to" }, + { + "id": "mockup-chat-start--source-channel", + "sourceId": "mockup-chat-start", + "targetId": "source-channel", + "relationType": "presented-by" + }, + { + "id": "mockup-chat-start--mockup-chat-answer", + "sourceId": "mockup-chat-start", + "targetId": "mockup-chat-answer", + "relationType": "navigates-to", + "label": "Ask" + }, + { + "id": "mockup-chat-answer--req-case-audit-trail", + "sourceId": "mockup-chat-answer", + "targetId": "req-case-audit-trail", + "relationType": "illustrates" + }, + { + "id": "mockup-chat-answer--source-channel", + "sourceId": "mockup-chat-answer", + "targetId": "source-channel", + "relationType": "presented-by" + }, + { + "id": "mockup-chat-answer--mockup-support-chat", + "sourceId": "mockup-chat-answer", + "targetId": "mockup-support-chat", + "relationType": "navigates-to", + "label": "Not resolved" + }, { "id": "mockup-support-chat--req-escalation-pickup", "sourceId": "mockup-support-chat", @@ -623,6 +789,25 @@ "targetId": "source-channel", "relationType": "presented-by" }, + { + "id": "mockup-support-chat--mockup-chat-human", + "sourceId": "mockup-support-chat", + "targetId": "mockup-chat-human", + "relationType": "navigates-to", + "label": "Agent joins" + }, + { + "id": "mockup-chat-human--req-escalation-pickup", + "sourceId": "mockup-chat-human", + "targetId": "req-escalation-pickup", + "relationType": "illustrates" + }, + { + "id": "mockup-chat-human--source-channel", + "sourceId": "mockup-chat-human", + "targetId": "source-channel", + "relationType": "presented-by" + }, { "id": "mockup-escalation-queue--req-escalation-pickup", "sourceId": "mockup-escalation-queue", @@ -665,6 +850,83 @@ "sourceId": "mockup-case-review", "targetId": "agent-console", "relationType": "presented-by" + }, + { + "id": "mockup-case-review--mockup-case-resolved", + "sourceId": "mockup-case-review", + "targetId": "mockup-case-resolved", + "relationType": "navigates-to", + "label": "Approve & send" + }, + { + "id": "mockup-case-resolved--req-case-audit-trail", + "sourceId": "mockup-case-resolved", + "targetId": "req-case-audit-trail", + "relationType": "illustrates" + }, + { + "id": "mockup-case-resolved--agent-console", + "sourceId": "mockup-case-resolved", + "targetId": "agent-console", + "relationType": "presented-by" + }, + { + "id": "mockup-case-resolved--mockup-case-audit", + "sourceId": "mockup-case-resolved", + "targetId": "mockup-case-audit", + "relationType": "navigates-to", + "label": "Audit trail" + }, + { + "id": "mockup-case-resolved--mockup-escalation-queue", + "sourceId": "mockup-case-resolved", + "targetId": "mockup-escalation-queue", + "relationType": "navigates-to", + "label": "Next case" + }, + { + "id": "mockup-case-audit--req-case-audit-trail", + "sourceId": "mockup-case-audit", + "targetId": "req-case-audit-trail", + "relationType": "illustrates" + }, + { + "id": "mockup-case-audit--agent-console", + "sourceId": "mockup-case-audit", + "targetId": "agent-console", + "relationType": "presented-by" + }, + { + "id": "mockup-kb-sources--req-kb-freshness", + "sourceId": "mockup-kb-sources", + "targetId": "req-kb-freshness", + "relationType": "illustrates" + }, + { + "id": "mockup-kb-sources--platform", + "sourceId": "mockup-kb-sources", + "targetId": "platform", + "relationType": "presented-by" + }, + { + "id": "mockup-kb-sources--mockup-quality-drift", + "sourceId": "mockup-kb-sources", + "targetId": "mockup-quality-drift", + "relationType": "navigates-to", + "label": "Quality" + }, + { + "id": "mockup-quality-drift--platform", + "sourceId": "mockup-quality-drift", + "targetId": "platform", + "relationType": "presented-by" + }, + { + "id": "mockup-quality-drift--mockup-kb-sources", + "sourceId": "mockup-quality-drift", + "targetId": "mockup-kb-sources", + "relationType": "navigates-to", + "label": "Fix stale source" } ], "sequences": [ diff --git a/hub/blueprint/bp-analytical-data-platform.radical b/hub/blueprint/bp-analytical-data-platform.radical index 3398bba..baed821 100644 --- a/hub/blueprint/bp-analytical-data-platform.radical +++ b/hub/blueprint/bp-analytical-data-platform.radical @@ -370,44 +370,153 @@ "consequences": "Version control, testing and documentation by default; CI/CD required for model deployment; learning curve for SQL-only teams.", "alternatives": "1. Spark jobs only — flexible, no conventions for tests or lineage. 2. Stored procedures — vendor-coupled and hard to review." }, + { + "id": "flow-analyst", + "type": "group", + "label": "Analyst journey", + "description": "Finding a dashboard, exploring the data behind it and asking for access.", + "x": 0, + "y": 1534, + "width": 1560, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-bi-home", + "type": "mockup", + "label": "Dashboards", + "description": "Governed dashboards by owning team.", + "parentId": "flow-analyst", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/dashboards", + "wireframe": "\n \n \n \n BI\n Datasets\n Explore\n Dashboards\n Dashboards\n \n Search dashboards\n \n \n \n Sales overview\n Sales Data\n \n \n \n Marketing funnel\n Growth\n \n \n \n Inventory health\n Ops\n \n \n \n Finance close\n Finance\n \n \n \n Support SLAs\n CX\n \n \n \n Product usage\n Product\n Starred and recently viewed first.\n" + }, { "id": "mockup-sales-dashboard", "type": "mockup", "label": "Sales Dashboard", "description": "Self-service dashboard showing how fresh the data is; restricted columns are masked for users whose role does not allow them.", - "x": 0, - "y": 1534, + "parentId": "flow-analyst", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/dashboards/sales", "wireframe": "\n \n \n \n BI\n Datasets\n Explore\n Dashboards\n Sales overview\n \n Region: EMEA ▾\n \n Last 30 days ▾\n \n Data as of 2 min ago\n \n Revenue\n €4.2M\n \n Orders\n 38,120\n \n Avg basket\n €110\n \n Revenue by day\n \n \n \n By channel\n \n \n \n \n \n \n Customer\n E-mail\n Lifetime value\n \n C-10293\n •••••• (restricted)\n €2,410\n \n C-10877\n •••••• (restricted)\n €1,980\n" }, + { + "id": "mockup-explore", + "type": "mockup", + "label": "Explore", + "description": "Self-service exploration; row and column policies from the catalogue still apply.", + "parentId": "flow-analyst", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/explore", + "wireframe": "\n \n \n \n BI\n Datasets\n Explore\n Dashboards\n \n curated.orders\n order_id\n amount\n customer_id\n \n customer_email\n region\n Explore curated.orders\n \n Group by: region\n \n Sum: amount\n \n Last 30 days\n \n \n \n \n \n \n customer_email is restricted for your role.\n Rows you can see are filtered to EMEA by policy.\n \n Request access\n \n Save chart\n" + }, { "id": "mockup-dataset-lineage", "type": "mockup", "label": "Dataset & Lineage", "description": "Catalogue page for a curated dataset with owner, classification and column-level lineage from source to dashboard.", - "x": 320, - "y": 1534, + "parentId": "flow-analyst", + "x": 990, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/datasets/:name/lineage", "wireframe": "\n \n \n \n Data Catalog\n Glossary\n Domains\n Search\n curated.orders\n \n Owner: Sales Data\n \n Confidential\n \n Contains PII\n Schema\n Lineage\n Quality\n \n \n \n raw.shop_orders\n order_id\n \n amount\n customer_id\n \n stg_orders\n order_id\n \n amount\n customer_id\n \n curated.orders\n order_id\n \n amount\n customer_id\n \n Sales overview\n order_id\n \n amount\n customer_id\n \n \n \n \n \n \n Column lineage for amount: raw.shop_orders.total → stg_orders.amount → curated.orders.amount\n \n Open in lineage API\n" }, + { + "id": "mockup-access-request", + "type": "mockup", + "label": "Request Access", + "description": "Access to restricted columns is requested from the dataset owner, time-boxed and audited.", + "parentId": "flow-analyst", + "x": 1310, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/datasets/:name/access", + "wireframe": "\n \n \n \n Request access\n curated.orders · column customer_email\n Classification: PII · Owner: Sales Data\n Why do you need it?\n \n Churn analysis for EMEA key accounts…\n For how long\n \n 30 days\n \n \n Cancel\n \n Send request\n" + }, + { + "id": "flow-data-engineer", + "type": "group", + "label": "Data engineer journey", + "description": "Keeping pipelines fresh and bad data out of curated tables.", + "x": 0, + "y": 1944, + "width": 1240, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-pipelines", + "type": "mockup", + "label": "Pipelines", + "description": "All pipelines with schedule and freshness.", + "parentId": "flow-data-engineer", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/pipelines", + "wireframe": "\n \n \n \n Orchestrator\n Quarantine\n Runs\n Pipelines\n Pipelines\n \n Pipeline\n Schedule\n Last run\n Freshness\n \n orders_stream\n streaming\n live\n \n 38 s behind\n \n \n orders_daily\n 02:00\n failed\n \n stale · 26 h\n \n customers\n hourly\n 12 min ago\n \n fresh\n \n products\n hourly\n 40 min ago\n \n fresh\n \n \n daily\n \n \n fresh\n" + }, { "id": "mockup-quality-runs", "type": "mockup", "label": "Data Quality Runs", "description": "Run results with the quality gate applied: failing records are quarantined, the affected models fail and the dataset owner is notified before anything reaches consumers.", - "x": 640, - "y": 1534, + "parentId": "flow-data-engineer", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/runs", "wireframe": "\n \n \n \n Orchestrator\n Quarantine\n Runs\n Pipelines\n Transformation runs — today\n \n Model\n Checks\n Result\n Owner\n \n \n curated.orders\n 14 / 16\n \n failed — 132 quarantined\n notified ✓\n \n curated.customers\n 9 / 9\n \n passed\n —\n \n agg.daily_sales\n —\n \n skipped (upstream failed)\n —\n \n curated.products\n 11 / 11\n \n passed\n —\n \n Failed checks — curated.orders\n ✕ unique(order_id) — 132 duplicate rows\n ✕ relationships(customer_id → curated.customers) — 4 orphans\n Failing rows were quarantined; downstream models were not updated.\n \n Open quarantine\n \n Re-run\n" + }, + { + "id": "mockup-quarantine", + "type": "mockup", + "label": "Quarantine", + "description": "Rows that failed the quality gate, held back until fixed, discarded or released.", + "parentId": "flow-data-engineer", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/quarantine/:run", + "wireframe": "\n \n \n \n Orchestrator\n Quarantine\n Runs\n Pipelines\n ← Runs\n Quarantine · curated.orders\n 132 rows held back from run 2026-03-24 02:00 · owner notified\n \n order_id\n amount\n customer_id\n Failed check\n \n 98213\n 42.10\n C-10293\n unique(order_id)\n \n 98213\n 42.10\n C-10293\n unique(order_id)\n \n 98277\n 15.00\n —\n not_null(customer_id)\n \n \n \n \n \n \n \n \n \n \n \n Download sample\n \n Discard rows\n \n Release after fix\n" + }, + { + "id": "mockup-freshness", + "type": "mockup", + "label": "Freshness Monitor", + "description": "Streaming latency against the {{INGESTION_LATENCY_SECONDS}} s target from event to raw zone.", + "parentId": "flow-data-engineer", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/freshness", + "wireframe": "\n \n \n \n Orchestrator\n Freshness\n Runs\n Pipelines\n Streaming freshness\n \n Target: queryable ≤ 60 s\n \n Event → raw zone latency (p95, last 24 h)\n \n \n \n 60 s\n \n Topic\n p95 now\n Breaches 24 h\n \n orders\n 38 s\n 1\n \n clickstream\n 22 s\n 0\n" } ], "relations": [ @@ -546,6 +655,19 @@ "targetId": "adr-dbt", "relationType": "implements" }, + { + "id": "mockup-bi-home--bi", + "sourceId": "mockup-bi-home", + "targetId": "bi", + "relationType": "presented-by" + }, + { + "id": "mockup-bi-home--mockup-sales-dashboard", + "sourceId": "mockup-bi-home", + "targetId": "mockup-sales-dashboard", + "relationType": "navigates-to", + "label": "Open" + }, { "id": "mockup-sales-dashboard--req-rls", "sourceId": "mockup-sales-dashboard", @@ -564,6 +686,13 @@ "targetId": "bi", "relationType": "presented-by" }, + { + "id": "mockup-sales-dashboard--mockup-explore", + "sourceId": "mockup-sales-dashboard", + "targetId": "mockup-explore", + "relationType": "navigates-to", + "label": "Explore" + }, { "id": "mockup-sales-dashboard--mockup-dataset-lineage", "sourceId": "mockup-sales-dashboard", @@ -571,6 +700,25 @@ "relationType": "navigates-to", "label": "View dataset" }, + { + "id": "mockup-explore--req-rls", + "sourceId": "mockup-explore", + "targetId": "req-rls", + "relationType": "illustrates" + }, + { + "id": "mockup-explore--bi", + "sourceId": "mockup-explore", + "targetId": "bi", + "relationType": "presented-by" + }, + { + "id": "mockup-explore--mockup-access-request", + "sourceId": "mockup-explore", + "targetId": "mockup-access-request", + "relationType": "navigates-to", + "label": "Request access" + }, { "id": "mockup-dataset-lineage--req-lineage", "sourceId": "mockup-dataset-lineage", @@ -584,11 +732,56 @@ "relationType": "presented-by" }, { - "id": "mockup-dataset-lineage--mockup-quality-runs", + "id": "mockup-dataset-lineage--mockup-access-request", "sourceId": "mockup-dataset-lineage", + "targetId": "mockup-access-request", + "relationType": "navigates-to", + "label": "Request access" + }, + { + "id": "mockup-access-request--req-rls", + "sourceId": "mockup-access-request", + "targetId": "req-rls", + "relationType": "illustrates" + }, + { + "id": "mockup-access-request--catalog", + "sourceId": "mockup-access-request", + "targetId": "catalog", + "relationType": "presented-by" + }, + { + "id": "mockup-access-request--mockup-dataset-lineage", + "sourceId": "mockup-access-request", + "targetId": "mockup-dataset-lineage", + "relationType": "navigates-to", + "label": "Send request" + }, + { + "id": "mockup-pipelines--req-freshness", + "sourceId": "mockup-pipelines", + "targetId": "req-freshness", + "relationType": "illustrates" + }, + { + "id": "mockup-pipelines--orchestrator", + "sourceId": "mockup-pipelines", + "targetId": "orchestrator", + "relationType": "presented-by" + }, + { + "id": "mockup-pipelines--mockup-quality-runs", + "sourceId": "mockup-pipelines", "targetId": "mockup-quality-runs", "relationType": "navigates-to", - "label": "Quality" + "label": "Open runs" + }, + { + "id": "mockup-pipelines--mockup-freshness", + "sourceId": "mockup-pipelines", + "targetId": "mockup-freshness", + "relationType": "navigates-to", + "label": "Freshness" }, { "id": "mockup-quality-runs--req-quality-gate", @@ -601,6 +794,44 @@ "sourceId": "mockup-quality-runs", "targetId": "orchestrator", "relationType": "presented-by" + }, + { + "id": "mockup-quality-runs--mockup-quarantine", + "sourceId": "mockup-quality-runs", + "targetId": "mockup-quarantine", + "relationType": "navigates-to", + "label": "Open quarantine" + }, + { + "id": "mockup-quarantine--req-quality-gate", + "sourceId": "mockup-quarantine", + "targetId": "req-quality-gate", + "relationType": "illustrates" + }, + { + "id": "mockup-quarantine--orchestrator", + "sourceId": "mockup-quarantine", + "targetId": "orchestrator", + "relationType": "presented-by" + }, + { + "id": "mockup-quarantine--mockup-quality-runs", + "sourceId": "mockup-quarantine", + "targetId": "mockup-quality-runs", + "relationType": "navigates-to", + "label": "Release after fix" + }, + { + "id": "mockup-freshness--req-freshness", + "sourceId": "mockup-freshness", + "targetId": "req-freshness", + "relationType": "illustrates" + }, + { + "id": "mockup-freshness--orchestrator", + "sourceId": "mockup-freshness", + "targetId": "orchestrator", + "relationType": "presented-by" } ] } diff --git a/hub/blueprint/bp-ecommerce-platform.radical b/hub/blueprint/bp-ecommerce-platform.radical index ec639df..272e825 100644 --- a/hub/blueprint/bp-ecommerce-platform.radical +++ b/hub/blueprint/bp-ecommerce-platform.radical @@ -493,44 +493,223 @@ "consequences": "Independent deployment and scaling; cross-service reports require projections or a data platform; joins across services are not possible in SQL.", "alternatives": "1. Shared database — simple joins, tight coupling. 2. Shared schema with per-service tables — coupling through migrations remains." }, + { + "id": "flow-shopper", + "type": "group", + "label": "Shopper journey", + "description": "From landing on the shop to tracking a delivered order.", + "x": 0, + "y": 2084, + "width": 1560, + "height": 630, + "collapsed": false + }, + { + "id": "mockup-home", + "type": "mockup", + "label": "Home", + "description": "Landing page: search, campaigns and categories.", + "parentId": "flow-shopper", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/", + "wireframe": "\n \n \n \n Shop\n Cart (0)\n Account\n Search\n \n Search shoes, jackets, brands…\n \n \n \n Spring running collection\n \n Shop now\n Popular categories\n \n \n \n Running\n \n \n \n Hiking\n \n \n \n Jackets\n \n \n \n Accessories\n \n" + }, + { + "id": "mockup-search-results", + "type": "mockup", + "label": "Search Results", + "description": "Filtered results from the search index (at most {{INDEX_LAG_SECONDS}} s behind the catalogue).", + "parentId": "flow-shopper", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/search", + "wireframe": "\n \n \n \n Shop\n Cart (0)\n Account\n \n trail running shoe\n 128 results\n \n Size 40 ✕\n \n Under €150 ✕\n \n In stock\n \n \n \n \n \n €129.00\n \n \n \n \n \n €99.00\n \n \n \n \n \n €145.00\n \n \n \n \n \n €119.00\n \n \n \n \n \n €89.00\n \n \n \n \n \n €139.00\n" + }, { "id": "mockup-product-page", "type": "mockup", "label": "Product Page", "description": "Product detail with live availability from the catalogue; \"Add to cart\" leads into checkout.", - "x": 0, - "y": 2084, + "parentId": "flow-shopper", + "x": 670, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/products/:slug", - "wireframe": "\n \n \n \n Shop\n Cart (2)\n Account\n Search\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Trail Running Shoe\n €129.00\n \n In stock — 12 left\n Size\n \n 38\n \n 39\n \n 40\n \n 41\n \n 42\n \n \n \n \n Add to cart\n You may also like\n \n \n \n \n \n \n \n \n \n \n \n \n" + "wireframe": "\n \n \n \n Shop\n Cart (0)\n Account\n Search\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Trail Running Shoe\n €129.00\n \n In stock — 12 left\n Size\n \n 38\n \n 39\n \n 40\n \n 41\n \n 42\n \n \n \n \n Add to cart\n You may also like\n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "id": "mockup-cart", + "type": "mockup", + "label": "Cart", + "description": "Cart with quantities and promo code; shipping is quoted at the next step.", + "parentId": "flow-shopper", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/cart", + "wireframe": "\n \n \n \n Shop\n Cart (1)\n Account\n Your cart\n \n \n \n \n \n \n 1\n \n €129.00\n \n Promo code\n \n SPRING10\n \n Apply\n Subtotal\n €129.00\n Shipping\n at next step\n \n Checkout\n ← Continue shopping\n" + }, + { + "id": "mockup-checkout-address", + "type": "mockup", + "label": "Checkout — Address", + "description": "First checkout step; like every step it answers within {{CHECKOUT_P95_MS}} ms at p95.", + "parentId": "flow-shopper", + "x": 1310, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/checkout/address", + "wireframe": "\n \n \n \n Shop — Checkout\n Help\n \n 1 Address\n \n 2 Shipping\n \n 3 Payment\n \n Order summary\n \n \n \n Subtotal\n €129.00\n Delivery address\n Full name\n \n Jane Doe\n Street and number\n \n Main St 12\n Postcode\n \n 10115\n City\n \n Berlin\n Country\n \n Germany\n \n \n Continue to shipping\n" + }, + { + "id": "mockup-checkout-shipping", + "type": "mockup", + "label": "Checkout — Shipping", + "description": "Live carrier quotes; still inside the checkout latency budget.", + "parentId": "flow-shopper", + "x": 30, + "y": 410, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/checkout/shipping", + "wireframe": "\n \n \n \n Shop — Checkout\n Help\n \n 1 Address ✓\n \n 2 Shipping\n \n 3 Payment\n \n Order summary\n \n \n \n Subtotal\n €129.00\n Shipping method\n \n \n \n Standard · 3–5 days\n €4.90\n \n \n Express · next day\n €12.90\n \n \n Pick-up point · 2–4 days\n free\n Rates are quoted live from the carrier.\n \n Continue to payment\n" }, { "id": "mockup-checkout", "type": "mockup", - "label": "Checkout", + "label": "Checkout — Payment", "description": "Final checkout step. Card fields are the provider's hosted fields, so card data never touches platform services; every step must answer within {{CHECKOUT_P95_MS}} ms at p95.", - "x": 320, - "y": 2084, + "parentId": "flow-shopper", + "x": 350, + "y": 410, "width": 220, "height": 190, "collapsed": false, - "screen": "/checkout", + "screen": "/checkout/payment", "wireframe": "\n \n \n \n Shop — Checkout\n Help\n \n 1 Address ✓\n \n 2 Shipping ✓\n \n 3 Payment\n Payment\n \n Card details — hosted by the payment provider\n Card number\n \n •••• •••• •••• ••••\n Expiry\n \n MM / YY\n CVC\n \n •••\n Card data goes straight to the provider — never through our servers.\n \n Pay €141.90\n ← Back to shipping\n \n Order summary\n \n \n \n \n \n \n \n \n \n \n \n Subtotal\n €129.00\n Shipping\n €4.90\n Tax\n €8.00\n Total\n €141.90\n" }, + { + "id": "mockup-payment-declined", + "type": "mockup", + "label": "Payment Declined", + "description": "Declined payment: nothing was captured, items stay reserved for the rest of the {{RESERVATION_MINUTES}}-minute hold, and retrying reuses the same idempotency key.", + "parentId": "flow-shopper", + "x": 670, + "y": 410, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/checkout/payment?declined", + "wireframe": "\n \n \n \n Shop — Checkout\n Help\n \n !\n Your card was declined\n You haven't been charged. Nothing was captured for this order.\n \n Order #10482 · €141.90\n Items stay reserved for 14:32 more\n \n Try another card\n Retrying never charges you twice.\n" + }, { "id": "mockup-order-confirmation", "type": "mockup", "label": "Order Confirmation", "description": "Shown once payment is captured. Refreshing or retrying never charges again — capture is keyed by the order id — and stock stays reserved for the order.", - "x": 640, - "y": 2084, + "parentId": "flow-shopper", + "x": 990, + "y": 410, "width": 220, "height": 190, "collapsed": false, "screen": "/orders/:id", "wireframe": "\n \n \n \n Shop\n Cart (0)\n Account\n \n \n Thank you — order #10482 is placed\n Payment captured once · confirmation e-mail on its way\n \n Placed ✓\n \n Paid ✓\n \n Packed\n \n Shipped\n \n Delivered\n \n \n \n \n \n \n 1 ×\n €129.00\n \n \n \n \n \n 1 ×\n €4.90\n Items are reserved for you — nothing else can be sold against them.\n \n Track order\n \n Continue shopping\n" + }, + { + "id": "mockup-order-tracking", + "type": "mockup", + "label": "Order Tracking", + "description": "Order status and carrier events after fulfilment books the shipment.", + "parentId": "flow-shopper", + "x": 1310, + "y": 410, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/account/orders/:id", + "wireframe": "\n \n \n \n Shop\n Cart (0)\n Account\n Orders\n Order #10482\n Placed 2 Apr · paid · 2 items\n \n Placed ✓\n \n Paid ✓\n \n Packed ✓\n \n Shipped ✓\n \n Delivered\n \n \n Out for delivery\n Today 09:12\n \n Arrived at local depot\n Yesterday 18:40\n \n Handed to carrier\n 3 Apr 14:05\n \n Packed at warehouse\n 3 Apr 10:21\n \n Carrier tracking page\n \n Return an item\n" + }, + { + "id": "flow-operations", + "type": "group", + "label": "Operations journey", + "description": "Back-office work: catalogue upkeep and order exceptions.", + "x": 0, + "y": 2794, + "width": 1240, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-bo-catalogue", + "type": "mockup", + "label": "Catalogue", + "description": "Products with price, stock and publication state.", + "parentId": "flow-operations", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/admin/catalogue", + "wireframe": "\n \n \n \n Back Office\n Customers\n Catalogue\n Orders\n Catalogue\n \n + New product\n \n Search products or SKUs\n \n \n Product\n Price\n Stock\n Status\n \n \n \n Trail Running Shoe\n €129.00\n 12\n \n live\n \n \n \n €99.00\n 0\n \n out of stock\n \n \n \n €145.00\n 34\n \n live\n \n \n \n €59.00\n 7\n \n draft\n \n \n \n €89.00\n 21\n \n live\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "id": "mockup-bo-product-editor", + "type": "mockup", + "label": "Product Editor", + "description": "Edit price, stock and copy; changes reach the search index within {{INDEX_LAG_SECONDS}} s.", + "parentId": "flow-operations", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/admin/catalogue/:sku", + "wireframe": "\n \n \n \n Back Office\n Customers\n Catalogue\n Orders\n ← Catalogue\n Trail Running Shoe\n \n \n \n Title\n \n Trail Running Shoe\n Price\n \n € 129.00\n Stock on hand\n \n 12\n Description\n \n Grippy outsole, 8 mm drop…\n Changes reach search within ~30 s.\n \n Unpublish\n \n Save changes\n" + }, + { + "id": "mockup-bo-orders", + "type": "mockup", + "label": "Orders", + "description": "Order list filtered to the exceptions staff act on.", + "parentId": "flow-operations", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/admin/orders", + "wireframe": "\n \n \n \n Back Office\n Customers\n Catalogue\n Orders\n Orders\n \n All\n \n Payment failed 3\n \n Awaiting stock 2\n \n Returns 5\n \n Order\n Customer\n Total\n Status\n \n #10482\n Jane Doe\n €141.90\n \n paid\n \n \n #10481\n \n €89.00\n \n payment failed\n \n #10480\n \n €212.40\n \n shipped\n \n #10479\n \n €54.00\n \n awaiting stock\n \n #10478\n \n €129.00\n \n delivered\n \n #10477\n \n €99.00\n \n return requested\n" + }, + { + "id": "mockup-bo-order-detail", + "type": "mockup", + "label": "Order Detail", + "description": "Payment attempts share one idempotency key, so staff can see nothing was captured twice; stock is released automatically when the hold expires.", + "parentId": "flow-operations", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/admin/orders/:id", + "wireframe": "\n \n \n \n Back Office\n Customers\n Catalogue\n Orders\n ← Orders\n Order #10481\n \n payment failed\n \n Payments\n \n Attempt\n Key\n Result\n \n 1\n ord-10481\n declined\n \n 2\n ord-10481\n declined\n Same idempotency key → never captured twice\n \n Stock\n Reserved until 10:47\n auto-release on expiry\n \n \n \n \n Contact customer\n \n Cancel order\n" } ], "relations": [ @@ -755,6 +934,32 @@ "targetId": "adr-hosted-payments", "relationType": "traces-to" }, + { + "id": "mockup-home--storefront", + "sourceId": "mockup-home", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-home--mockup-search-results", + "sourceId": "mockup-home", + "targetId": "mockup-search-results", + "relationType": "navigates-to", + "label": "Search" + }, + { + "id": "mockup-search-results--storefront", + "sourceId": "mockup-search-results", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-search-results--mockup-product-page", + "sourceId": "mockup-search-results", + "targetId": "mockup-product-page", + "relationType": "navigates-to", + "label": "Open product" + }, { "id": "mockup-product-page--storefront", "sourceId": "mockup-product-page", @@ -762,12 +967,63 @@ "relationType": "presented-by" }, { - "id": "mockup-product-page--mockup-checkout", + "id": "mockup-product-page--mockup-cart", "sourceId": "mockup-product-page", - "targetId": "mockup-checkout", + "targetId": "mockup-cart", + "relationType": "navigates-to", + "label": "Add to cart" + }, + { + "id": "mockup-cart--storefront", + "sourceId": "mockup-cart", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-cart--mockup-checkout-address", + "sourceId": "mockup-cart", + "targetId": "mockup-checkout-address", "relationType": "navigates-to", "label": "Checkout" }, + { + "id": "mockup-checkout-address--req-checkout-latency", + "sourceId": "mockup-checkout-address", + "targetId": "req-checkout-latency", + "relationType": "illustrates" + }, + { + "id": "mockup-checkout-address--storefront", + "sourceId": "mockup-checkout-address", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-checkout-address--mockup-checkout-shipping", + "sourceId": "mockup-checkout-address", + "targetId": "mockup-checkout-shipping", + "relationType": "navigates-to", + "label": "Continue" + }, + { + "id": "mockup-checkout-shipping--req-checkout-latency", + "sourceId": "mockup-checkout-shipping", + "targetId": "req-checkout-latency", + "relationType": "illustrates" + }, + { + "id": "mockup-checkout-shipping--storefront", + "sourceId": "mockup-checkout-shipping", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-checkout-shipping--mockup-checkout", + "sourceId": "mockup-checkout-shipping", + "targetId": "mockup-checkout", + "relationType": "navigates-to", + "label": "Continue" + }, { "id": "mockup-checkout--req-checkout-latency", "sourceId": "mockup-checkout", @@ -780,6 +1036,12 @@ "targetId": "req-pci-scope", "relationType": "illustrates" }, + { + "id": "mockup-checkout--req-stock-reservation", + "sourceId": "mockup-checkout", + "targetId": "req-stock-reservation", + "relationType": "illustrates" + }, { "id": "mockup-checkout--storefront", "sourceId": "mockup-checkout", @@ -793,6 +1055,38 @@ "relationType": "navigates-to", "label": "Pay" }, + { + "id": "mockup-checkout--mockup-payment-declined", + "sourceId": "mockup-checkout", + "targetId": "mockup-payment-declined", + "relationType": "navigates-to", + "label": "Card declined" + }, + { + "id": "mockup-payment-declined--req-payment-once", + "sourceId": "mockup-payment-declined", + "targetId": "req-payment-once", + "relationType": "illustrates" + }, + { + "id": "mockup-payment-declined--req-stock-reservation", + "sourceId": "mockup-payment-declined", + "targetId": "req-stock-reservation", + "relationType": "illustrates" + }, + { + "id": "mockup-payment-declined--storefront", + "sourceId": "mockup-payment-declined", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-payment-declined--mockup-checkout", + "sourceId": "mockup-payment-declined", + "targetId": "mockup-checkout", + "relationType": "navigates-to", + "label": "Try another card" + }, { "id": "mockup-order-confirmation--req-payment-once", "sourceId": "mockup-order-confirmation", @@ -810,6 +1104,97 @@ "sourceId": "mockup-order-confirmation", "targetId": "storefront", "relationType": "presented-by" + }, + { + "id": "mockup-order-confirmation--mockup-order-tracking", + "sourceId": "mockup-order-confirmation", + "targetId": "mockup-order-tracking", + "relationType": "navigates-to", + "label": "Track order" + }, + { + "id": "mockup-order-confirmation--mockup-home", + "sourceId": "mockup-order-confirmation", + "targetId": "mockup-home", + "relationType": "navigates-to", + "label": "Continue shopping" + }, + { + "id": "mockup-order-tracking--storefront", + "sourceId": "mockup-order-tracking", + "targetId": "storefront", + "relationType": "presented-by" + }, + { + "id": "mockup-bo-catalogue--backoffice", + "sourceId": "mockup-bo-catalogue", + "targetId": "backoffice", + "relationType": "presented-by" + }, + { + "id": "mockup-bo-catalogue--mockup-bo-product-editor", + "sourceId": "mockup-bo-catalogue", + "targetId": "mockup-bo-product-editor", + "relationType": "navigates-to", + "label": "Edit product" + }, + { + "id": "mockup-bo-catalogue--mockup-bo-orders", + "sourceId": "mockup-bo-catalogue", + "targetId": "mockup-bo-orders", + "relationType": "navigates-to", + "label": "Orders" + }, + { + "id": "mockup-bo-product-editor--backoffice", + "sourceId": "mockup-bo-product-editor", + "targetId": "backoffice", + "relationType": "presented-by" + }, + { + "id": "mockup-bo-product-editor--mockup-bo-catalogue", + "sourceId": "mockup-bo-product-editor", + "targetId": "mockup-bo-catalogue", + "relationType": "navigates-to", + "label": "Save changes" + }, + { + "id": "mockup-bo-orders--backoffice", + "sourceId": "mockup-bo-orders", + "targetId": "backoffice", + "relationType": "presented-by" + }, + { + "id": "mockup-bo-orders--mockup-bo-order-detail", + "sourceId": "mockup-bo-orders", + "targetId": "mockup-bo-order-detail", + "relationType": "navigates-to", + "label": "Open order" + }, + { + "id": "mockup-bo-order-detail--req-payment-once", + "sourceId": "mockup-bo-order-detail", + "targetId": "req-payment-once", + "relationType": "illustrates" + }, + { + "id": "mockup-bo-order-detail--req-stock-reservation", + "sourceId": "mockup-bo-order-detail", + "targetId": "req-stock-reservation", + "relationType": "illustrates" + }, + { + "id": "mockup-bo-order-detail--backoffice", + "sourceId": "mockup-bo-order-detail", + "targetId": "backoffice", + "relationType": "presented-by" + }, + { + "id": "mockup-bo-order-detail--mockup-bo-orders", + "sourceId": "mockup-bo-order-detail", + "targetId": "mockup-bo-orders", + "relationType": "navigates-to", + "label": "Back to orders" } ] } diff --git a/hub/blueprint/bp-fintech-ledger-platform.radical b/hub/blueprint/bp-fintech-ledger-platform.radical index a63b174..ea55cc0 100644 --- a/hub/blueprint/bp-fintech-ledger-platform.radical +++ b/hub/blueprint/bp-fintech-ledger-platform.radical @@ -393,44 +393,206 @@ "consequences": "A balance can update the instant a posting is accepted, before the bank settles — which means \"posted\" and \"reconciled\" must be visibly different states, or a customer or support agent will confuse a provisional balance with a settled one.", "alternatives": "1. Bank/processor as source of truth, product waits for their confirmation — correct by construction, but the customer's balance now lags the actual action by the settlement delay. 2. Internal ledger as source of truth (chosen). 3. Dual-write both, resolve conflicts on read — no clear authority, hardest of the three to reason about when they disagree." }, + { + "id": "flow-customer", + "type": "group", + "label": "Customer journey", + "description": "Checking the account, reading the statement and disputing a transaction.", + "x": 0, + "y": 2084, + "width": 1560, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-accounts-overview", + "type": "mockup", + "label": "Accounts Overview", + "description": "Balances (projected from the journal) and recent activity.", + "parentId": "flow-customer", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/accounts", + "wireframe": "\n \n \n \n Bank\n Help\n Statements\n Accounts\n Good morning, Jane\n \n Current account\n €1,982.13\n \n Savings\n €8,400.00\n Recent activity\n \n Date\n Description\n Amount\n \n 24 Mar\n \n \n \n 18 Mar\n \n \n \n 12 Mar\n Transfer to savings\n −500.00\n \n 07 Mar\n Salary — Acme Ltd\n +3,200.00\n \n View statements\n \n New transfer\n" + }, { "id": "mockup-statement", "type": "mockup", "label": "Account Statement", "description": "Customer statement generated strictly from balances replayed through a fixed cut-off; each line links to a dispute.", - "x": 0, - "y": 2084, + "parentId": "flow-customer", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/accounts/:id/statement", "wireframe": "\n \n \n \n Bank\n Help\n Statements\n Accounts\n Statement — March 2026\n Balances as of the cut-off: 31 Mar 2026, 23:59:59 UTC\n \n Opening balance\n €2,410.55\n \n Closing balance\n €1,982.13\n \n Date\n Description\n Amount\n \n \n 03 Mar\n Card — Green Grocer\n −42.10\n Dispute\n \n 07 Mar\n Salary — Acme Ltd\n +3,200.00\n Dispute\n \n 12 Mar\n Transfer to savings\n −500.00\n Dispute\n \n 18 Mar\n \n \n Dispute\n \n 24 Mar\n \n \n Dispute\n \n Download PDF\n" }, + { + "id": "mockup-transaction-detail", + "type": "mockup", + "label": "Transaction Detail", + "description": "One statement line with its settlement status and the journal entry behind it.", + "parentId": "flow-customer", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/transactions/:id", + "wireframe": "\n \n \n \n Bank\n Help\n Statements\n Accounts\n ← March statement\n −€42.10\n Card — Green Grocer · 03 Mar 2026, 18:22\n \n Detail\n \n \n Card\n •••• 4421\n \n Merchant\n Green Grocer, Berlin\n \n Status\n Settled 04 Mar\n \n Journal entry\n JE-2026-03-004218\n Don't recognise it or amount is wrong?\n \n Dispute this transaction\n" + }, { "id": "mockup-dispute", "type": "mockup", "label": "Dispute a Transaction", "description": "Opens an investigation the moment it is submitted; the customer gets a resolution or explanation within {{DISPUTE_INVESTIGATION_DAYS}} days.", - "x": 320, - "y": 2084, + "parentId": "flow-customer", + "x": 990, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/disputes/new", "wireframe": "\n \n \n \n Bank\n Help\n Statements\n Accounts\n Dispute a transaction\n \n 03 Mar 2026 · Card — Green Grocer · −€42.10\n What went wrong?\n \n I didn't make this payment\n \n \n I was charged the wrong amount\n \n I didn't receive the goods or service\n Tell us what happened\n \n Charged €42.10, receipt says €24.10…\n We'll open an investigation right away and reply within the investigation window.\n \n Cancel\n \n Submit dispute\n" }, + { + "id": "mockup-dispute-status", + "type": "mockup", + "label": "Dispute Status", + "description": "Open dispute with the time left until the {{DISPUTE_INVESTIGATION_DAYS}}-day resolution deadline.", + "parentId": "flow-customer", + "x": 1310, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/disputes/:id", + "wireframe": "\n \n \n \n Bank\n Help\n Statements\n Accounts\n \n \n Dispute DSP-8812 is open\n Card — Green Grocer · −€42.10\n \n Submitted ✓\n \n Investigating\n \n Resolved\n Time to resolution\n day 2 of 10\n \n \n We'll write to you with a resolution or explanation before the deadline.\n \n Latest update\n \n \n \n Add information\n" + }, + { + "id": "flow-finance-ops", + "type": "group", + "label": "Finance Ops journey", + "description": "Clearing reconciliation exceptions without ever editing the journal.", + "x": 0, + "y": 2494, + "width": 1560, + "height": 330, + "collapsed": false + }, { "id": "mockup-reconciliation-exceptions", "type": "mockup", "label": "Reconciliation Exceptions", "description": "Finance Ops queue of settlement events not matched within {{RECONCILIATION_LAG_MIN}} minutes. Corrections are compensating entries that must balance before they can be submitted — the journal itself is never edited.", - "x": 640, - "y": 2084, + "parentId": "flow-finance-ops", + "x": 30, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/ops/exceptions", - "wireframe": "\n \n \n \n Finance Ops\n Reports\n Journal\n Exceptions (7)\n \n Unmatched 5\n \n Amount mismatch 2\n \n Older than 24 h 1\n \n Settlement ref\n Amount\n Age\n Status\n \n \n STL-88213\n 1,250.00\n 42 min\n \n unmatched\n \n STL-88207\n 980.40\n 1 h 10\n \n amount mismatch\n \n STL-88190\n 75.00\n 26 h\n \n older than 24 h\n \n STL-88188\n \n \n \n unmatched\n \n Compensating entry for STL-88213\n Debit account\n \n Suspense 1990\n \n \n 1,250.00\n Credit account\n \n Customer 4410-22\n \n \n 1,250.00\n \n Balanced ✓\n \n Submit for approval\n" + "wireframe": "\n \n \n \n Finance Ops\n Reports\n Journal\n Exceptions (7)\n \n Unmatched 5\n \n Amount mismatch 2\n \n Older than 24 h 1\n \n Settlement ref\n Amount\n Age\n Status\n \n \n STL-88213\n 1,250.00\n 42 min\n \n unmatched\n \n STL-88207\n 980.40\n 1 h 10\n \n amount mismatch\n \n STL-88190\n 75.00\n 26 h\n \n older than 24 h\n \n STL-88188\n \n \n \n unmatched\n \n STL-88186\n \n \n \n unmatched\n \n STL-88181\n \n \n \n amount mismatch\n Matched within 15 min (rolling 24 h)\n 99.6%\n \n \n" + }, + { + "id": "mockup-exception-detail", + "type": "mockup", + "label": "Exception Detail", + "description": "Settlement event next to candidate journal entries; a manual match or a compensating entry clears it.", + "parentId": "flow-finance-ops", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/ops/exceptions/:id", + "wireframe": "\n \n \n \n Finance Ops\n Reports\n Journal\n Exceptions (7)\n ← Exceptions\n STL-88213 · €1,250.00\n \n unmatched · 42 min\n \n Bank settlement\n Value date 24 Mar\n Ref ACME-INV-2231\n Counterparty Acme Ltd\n \n Candidate journal entries\n \n \n JE-…04512 · 1,250.00\n \n JE-…04498 · 1,205.00\n No exact match? Post a compensating entry instead of editing the journal.\n \n Compensating entry…\n \n Confirm match\n" + }, + { + "id": "mockup-compensating-entry", + "type": "mockup", + "label": "Compensating Entry", + "description": "Double-entry form that cannot be submitted unless debits equal credits.", + "parentId": "flow-finance-ops", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/ops/entries/new", + "wireframe": "\n \n \n \n Finance Ops\n Reports\n Journal\n Exceptions (7)\n Compensating entry for STL-88213\n \n Account\n Debit\n Credit\n \n Suspense 1990\n 1,250.00\n \n \n Customer 4410-22\n \n 1,250.00\n \n \n \n \n Totals\n 1,250.00\n 1,250.00\n Reason (required)\n \n Bank ref mismatch — see STL-88213\n \n Balanced ✓\n \n Cancel\n \n Submit for approval\n" + }, + { + "id": "mockup-entry-approval", + "type": "mockup", + "label": "Four-Eyes Approval", + "description": "A second person approves before the entry is posted; nobody approves their own entries.", + "parentId": "flow-finance-ops", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/ops/approvals/:id", + "wireframe": "\n \n \n \n Finance Ops\n Journal\n Approvals (1)\n Exceptions (7)\n Approve compensating entry\n Submitted by M. Weber · 10:14 · you cannot approve your own entries\n \n Account\n Debit\n Credit\n \n Suspense 1990\n 1,250.00\n \n \n Customer 4410-22\n \n 1,250.00\n \n Reason\n Bank ref mismatch — see STL-88213\n Once approved it is posted as a new, immutable journal entry.\n \n Reject\n \n Approve & post\n" + }, + { + "id": "mockup-journal-entry", + "type": "mockup", + "label": "Journal Entry", + "description": "Posted entries are immutable; the only correction is another entry.", + "parentId": "flow-finance-ops", + "x": 1310, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/ops/journal/:id", + "wireframe": "\n \n \n \n Finance Ops\n Reports\n Journal\n Exceptions\n JE-2026-03-004731\n \n posted · immutable\n 24 Mar 2026 10:21 · compensates STL-88213 · approved by A. Novak\n \n Account\n Debit\n Credit\n \n Suspense 1990\n 1,250.00\n \n \n Customer 4410-22\n \n 1,250.00\n Audit trail\n 10:14\n Created by M. Weber\n 10:20\n Approved by A. Novak\n 10:21\n Posted; balances re-projected\n Entries are never edited — corrections are new entries.\n \n Reverse entry…\n" + }, + { + "id": "flow-compliance", + "type": "group", + "label": "Compliance journey", + "description": "Auditing postings and signing off regulatory reporting.", + "x": 0, + "y": 2904, + "width": 600, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-audit-trail", + "type": "mockup", + "label": "Audit Trail", + "description": "Who did what to which entry, searchable and exportable.", + "parentId": "flow-compliance", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/audit", + "wireframe": "\n \n \n \n Finance Ops\n Reports\n Audit\n Journal\n Exceptions\n Audit trail\n \n Account, entry or user\n \n Last 30 days\n \n \n Search\n \n When\n Who\n Action\n Entry\n \n 24 Mar 10:21\n A. Novak\n Approved & posted\n JE-…4731\n \n 24 Mar 10:14\n M. Weber\n Created compensating\n JE-…4731\n \n 23 Mar 16:02\n system\n Nightly reconciliation\n report\n \n 23 Mar 11:40\n \n \n \n \n 22 Mar 09:12\n \n \n \n \n Export CSV\n" + }, + { + "id": "mockup-regulatory-reports", + "type": "mockup", + "label": "Regulatory Reports", + "description": "Scheduled regulatory extracts built from the journal at a fixed cut-off, signed off by Compliance.", + "parentId": "flow-compliance", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/reports", + "wireframe": "\n \n \n \n Finance Ops\n Reports\n Audit\n Journal\n Exceptions\n Regulatory reports\n \n Report\n Period\n Due\n Status\n \n \n Liquidity return\n Mar 2026\n 10 Apr\n \n awaiting sign-off\n \n Safeguarding recon\n Mar 2026\n 05 Apr\n \n submitted\n \n Large exposures\n Q1 2026\n 30 Apr\n \n draft\n \n Liquidity return — Mar 2026\n Built from the journal as of cut-off 31 Mar 23:59:59 UTC\n Reconciliation exceptions open: 0\n \n Preview extract\n \n Sign off & submit\n" } ], "relations": [ @@ -592,6 +754,19 @@ "targetId": "balance-projector", "relationType": "constrains" }, + { + "id": "mockup-accounts-overview--statement-service", + "sourceId": "mockup-accounts-overview", + "targetId": "statement-service", + "relationType": "presented-by" + }, + { + "id": "mockup-accounts-overview--mockup-statement", + "sourceId": "mockup-accounts-overview", + "targetId": "mockup-statement", + "relationType": "navigates-to", + "label": "View statements" + }, { "id": "mockup-statement--req-statement-accuracy-cutoff", "sourceId": "mockup-statement", @@ -605,8 +780,21 @@ "relationType": "presented-by" }, { - "id": "mockup-statement--mockup-dispute", + "id": "mockup-statement--mockup-transaction-detail", "sourceId": "mockup-statement", + "targetId": "mockup-transaction-detail", + "relationType": "navigates-to", + "label": "Open transaction" + }, + { + "id": "mockup-transaction-detail--statement-service", + "sourceId": "mockup-transaction-detail", + "targetId": "statement-service", + "relationType": "presented-by" + }, + { + "id": "mockup-transaction-detail--mockup-dispute", + "sourceId": "mockup-transaction-detail", "targetId": "mockup-dispute", "relationType": "navigates-to", "label": "Dispute" @@ -623,6 +811,25 @@ "targetId": "statement-service", "relationType": "presented-by" }, + { + "id": "mockup-dispute--mockup-dispute-status", + "sourceId": "mockup-dispute", + "targetId": "mockup-dispute-status", + "relationType": "navigates-to", + "label": "Submit dispute" + }, + { + "id": "mockup-dispute-status--req-dispute-investigation-sla", + "sourceId": "mockup-dispute-status", + "targetId": "req-dispute-investigation-sla", + "relationType": "illustrates" + }, + { + "id": "mockup-dispute-status--statement-service", + "sourceId": "mockup-dispute-status", + "targetId": "statement-service", + "relationType": "presented-by" + }, { "id": "mockup-reconciliation-exceptions--req-reconciliation-freshness", "sourceId": "mockup-reconciliation-exceptions", @@ -640,6 +847,121 @@ "sourceId": "mockup-reconciliation-exceptions", "targetId": "finance-ops-console", "relationType": "presented-by" + }, + { + "id": "mockup-reconciliation-exceptions--mockup-exception-detail", + "sourceId": "mockup-reconciliation-exceptions", + "targetId": "mockup-exception-detail", + "relationType": "navigates-to", + "label": "Open exception" + }, + { + "id": "mockup-exception-detail--req-reconciliation-freshness", + "sourceId": "mockup-exception-detail", + "targetId": "req-reconciliation-freshness", + "relationType": "illustrates" + }, + { + "id": "mockup-exception-detail--finance-ops-console", + "sourceId": "mockup-exception-detail", + "targetId": "finance-ops-console", + "relationType": "presented-by" + }, + { + "id": "mockup-exception-detail--mockup-compensating-entry", + "sourceId": "mockup-exception-detail", + "targetId": "mockup-compensating-entry", + "relationType": "navigates-to", + "label": "Compensating entry" + }, + { + "id": "mockup-exception-detail--mockup-reconciliation-exceptions", + "sourceId": "mockup-exception-detail", + "targetId": "mockup-reconciliation-exceptions", + "relationType": "navigates-to", + "label": "Confirm match" + }, + { + "id": "mockup-compensating-entry--req-balanced-entry-enforcement", + "sourceId": "mockup-compensating-entry", + "targetId": "req-balanced-entry-enforcement", + "relationType": "illustrates" + }, + { + "id": "mockup-compensating-entry--finance-ops-console", + "sourceId": "mockup-compensating-entry", + "targetId": "finance-ops-console", + "relationType": "presented-by" + }, + { + "id": "mockup-compensating-entry--mockup-entry-approval", + "sourceId": "mockup-compensating-entry", + "targetId": "mockup-entry-approval", + "relationType": "navigates-to", + "label": "Submit for approval" + }, + { + "id": "mockup-entry-approval--req-balanced-entry-enforcement", + "sourceId": "mockup-entry-approval", + "targetId": "req-balanced-entry-enforcement", + "relationType": "illustrates" + }, + { + "id": "mockup-entry-approval--finance-ops-console", + "sourceId": "mockup-entry-approval", + "targetId": "finance-ops-console", + "relationType": "presented-by" + }, + { + "id": "mockup-entry-approval--mockup-journal-entry", + "sourceId": "mockup-entry-approval", + "targetId": "mockup-journal-entry", + "relationType": "navigates-to", + "label": "Approve & post" + }, + { + "id": "mockup-journal-entry--req-balanced-entry-enforcement", + "sourceId": "mockup-journal-entry", + "targetId": "req-balanced-entry-enforcement", + "relationType": "illustrates" + }, + { + "id": "mockup-journal-entry--finance-ops-console", + "sourceId": "mockup-journal-entry", + "targetId": "finance-ops-console", + "relationType": "presented-by" + }, + { + "id": "mockup-journal-entry--mockup-reconciliation-exceptions", + "sourceId": "mockup-journal-entry", + "targetId": "mockup-reconciliation-exceptions", + "relationType": "navigates-to", + "label": "Exception cleared" + }, + { + "id": "mockup-audit-trail--finance-ops-console", + "sourceId": "mockup-audit-trail", + "targetId": "finance-ops-console", + "relationType": "presented-by" + }, + { + "id": "mockup-audit-trail--mockup-regulatory-reports", + "sourceId": "mockup-audit-trail", + "targetId": "mockup-regulatory-reports", + "relationType": "navigates-to", + "label": "Reports" + }, + { + "id": "mockup-regulatory-reports--req-statement-accuracy-cutoff", + "sourceId": "mockup-regulatory-reports", + "targetId": "req-statement-accuracy-cutoff", + "relationType": "illustrates" + }, + { + "id": "mockup-regulatory-reports--finance-ops-console", + "sourceId": "mockup-regulatory-reports", + "targetId": "finance-ops-console", + "relationType": "presented-by" } ], "sequences": [ diff --git a/hub/blueprint/bp-marketplace-platform.radical b/hub/blueprint/bp-marketplace-platform.radical index abe62fc..70963be 100644 --- a/hub/blueprint/bp-marketplace-platform.radical +++ b/hub/blueprint/bp-marketplace-platform.radical @@ -415,26 +415,187 @@ "consequences": "Buyers are protected as fast as the platform can detect a problem; the investigation SLA becomes a trust-critical metric in its own right, since it's now the only thing standing between a wrongly-flagged seller and lost income.", "alternatives": "1. Investigate first, act after — protects sellers from false positives, but a genuine bad actor keeps selling and collecting funds during the investigation window. 2. Suspend first, investigate after (chosen). 3. Community reporting only, no proactive detection — cheapest, but slowest and worst for buyer trust. 4. Permanent ban on first signal, no reinstatement path — fastest, but the harshest possible cost of a false positive." }, + { + "id": "flow-buyer", + "type": "group", + "label": "Buyer journey", + "description": "Finding an item, paying with buyer protection and disputing it.", + "x": 0, + "y": 2084, + "width": 1560, + "height": 630, + "collapsed": false + }, + { + "id": "mockup-mp-search", + "type": "mockup", + "label": "Search", + "description": "Only approved, live listings appear in search.", + "parentId": "flow-buyer", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/search", + "wireframe": "\n \n \n \n Marketplace\n Account\n Orders\n Sell\n \n armchair\n 64 listings\n \n Furniture ✕\n \n Within 50 km\n \n Price ▾\n \n \n \n \n €180\n VintageCo\n \n \n \n \n €95\n jan_k\n \n \n \n \n €240\n Loft&Co\n \n \n \n \n €60\n mira\n \n \n \n \n €130\n OakHaus\n \n \n \n \n €310\n retro.de\n" + }, + { + "id": "mockup-mp-listing", + "type": "mockup", + "label": "Listing", + "description": "Listing page with seller reputation and buyer protection terms.", + "parentId": "flow-buyer", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/listings/:id", + "wireframe": "\n \n \n \n Marketplace\n Account\n Orders\n Sell\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Mid-century armchair\n €180.00\n Used — good condition · Berlin\n \n \n \n \n VintageCo\n ★ 4.8 · 212 sales\n \n Buy now\n \n Message seller\n Buyer protection: your payment is held until you confirm the item arrived as described.\n" + }, + { + "id": "mockup-mp-checkout", + "type": "mockup", + "label": "Checkout", + "description": "Payment goes to the payments facilitator, which holds the funds; the platform never custodies them.", + "parentId": "flow-buyer", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/checkout", + "wireframe": "\n \n \n \n Marketplace — Checkout\n Help\n Checkout\n \n \n \n \n \n €180.00\n Delivery\n \n Courier · 2–3 days · €19.00\n \n Card\n \n •••• •••• •••• 4421\n \n You pay\n Item\n €180.00\n Delivery\n €19.00\n Protection\n €2.40\n Total\n €201.40\n Funds are held by our payment partner — the seller is paid only after the\n protection window ends. We never hold your money ourselves.\n \n Pay €201.40\n" + }, + { + "id": "mockup-order-dispute", + "type": "mockup", + "label": "Order & Buyer Protection", + "description": "Buyer view of an order: funds stay held for {{DISPUTE_WINDOW_DAYS}} days after delivery, and opening a dispute freezes the payout.", + "parentId": "flow-buyer", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/orders/:id", + "wireframe": "\n \n \n \n Marketplace\n Account\n Orders\n Search\n Order #58213\n Sold by VintageCo · delivered 2 Apr\n \n \n \n \n \n €180.00\n Where your money is\n \n Paid ✓\n \n Delivered ✓\n \n Held for buyer protection\n \n Released to seller\n Buyer protection window\n 3 of 7 days\n \n \n Something wrong? Opening a dispute freezes the payout until it is resolved.\n \n Open a dispute\n \n Confirm all good\n" + }, + { + "id": "mockup-mp-open-dispute", + "type": "mockup", + "label": "Open a Dispute", + "description": "Opening a dispute inside the {{DISPUTE_WINDOW_DAYS}}-day window freezes the seller payout.", + "parentId": "flow-buyer", + "x": 1310, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/orders/:id/dispute", + "wireframe": "\n \n \n \n Marketplace\n Account\n Orders\n Search\n Open a dispute — order #58213\n \n Item didn't arrive\n \n \n Item not as described\n \n Item arrived damaged\n What happened?\n \n The left armrest is cracked…\n \n + Add photos of the item\n \n The seller's payout is frozen while the dispute is open.\n \n Cancel\n \n Open dispute\n" + }, + { + "id": "mockup-mp-dispute-status", + "type": "mockup", + "label": "Dispute Status", + "description": "Conversation and status while Trust & Safety reviews.", + "parentId": "flow-buyer", + "x": 30, + "y": 410, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/disputes/:id", + "wireframe": "\n \n \n \n Marketplace\n Account\n Orders\n Search\n Dispute on order #58213\n \n Opened ✓\n \n Seller responded ✓\n \n Under review\n \n Resolved\n \n Payout to the seller is on hold until this is resolved.\n \n \n \n \n \n \n \n \n \n Write a message…\n \n Send\n" + }, + { + "id": "flow-seller", + "type": "group", + "label": "Seller journey", + "description": "Listing items, getting them approved and getting paid.", + "x": 0, + "y": 2794, + "width": 1240, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-seller-dashboard", + "type": "mockup", + "label": "Seller Dashboard", + "description": "What needs the seller's attention today.", + "parentId": "flow-seller", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/sell", + "wireframe": "\n \n \n \n Marketplace — Sell\n Payouts\n Orders\n Listings\n Seller dashboard\n \n + New listing\n \n Live listings\n 23\n \n In review\n 2\n \n Next payout\n €412.50\n To do\n \n \n \n \n \n \n ship by tomorrow\n \n \n \n \n \n \n \n rejected — see reason\n \n \n \n \n \n \n \n buyer message\n \n" + }, { "id": "mockup-new-listing", "type": "mockup", "label": "New Listing", "description": "Seller creates a listing; it goes into review and only appears in search once approved (within {{LISTING_REVIEW_SLA_HOURS}} h).", - "x": 0, - "y": 2084, + "parentId": "flow-seller", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/sell/listings/new", "wireframe": "\n \n \n \n Marketplace — Sell\n Payouts\n My listings\n Create a listing\n \n + Add photos\n up to 10\n Title\n \n Mid-century armchair\n Price\n \n € 180.00\n Category\n \n Furniture\n \n Description\n \n Condition, dimensions, pickup…\n \n Listings are reviewed against marketplace policy before they appear in search.\n You keep the price minus the platform commission.\n \n Save draft\n \n Submit for review\n" }, + { + "id": "mockup-my-listings", + "type": "mockup", + "label": "My Listings", + "description": "Listing states from the moderation workflow — in review, live, rejected with a reason.", + "parentId": "flow-seller", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/sell/listings", + "wireframe": "\n \n \n \n Marketplace — Sell\n Payouts\n Orders\n Listings\n My listings\n All 25\n Live 23\n In review 1\n \n Rejected 1\n \n \n \n \n \n \n \n \n in review · 3 h\n \n \n \n \n \n \n \n live\n \n \n \n \n \n \n \n live\n \n \n Rejected: “Designer lamp”\n Reason: brand name in title without proof of authenticity.\n \n Edit & resubmit\n" + }, + { + "id": "mockup-seller-payouts", + "type": "mockup", + "label": "Payouts", + "description": "Per-order split: {{COMMISSION_PCT}}% commission deducted, payouts held for the protection window and frozen while a dispute is open.", + "parentId": "flow-seller", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/sell/payouts", + "wireframe": "\n \n \n \n Marketplace — Sell\n Payouts\n Orders\n Listings\n Payouts\n \n Available\n €412.50\n \n Held (protection)\n €880.00\n \n Frozen (dispute)\n €180.00\n \n Order\n Sale\n Commission\n Payout\n Status\n \n #58240\n €250.00\n −€37.50\n €212.50\n \n available\n \n #58231\n €235.29\n −€35.29\n €200.00\n \n available\n \n #58227\n €880.00\n −€132.00\n €748.00\n \n held · 4 days\n \n #58213\n €180.00\n −€27.00\n €153.00\n \n frozen\n Commission is 15% of the item price; every split is recorded per order.\n \n Withdraw €412.50\n" + }, + { + "id": "flow-trust-safety", + "type": "group", + "label": "Trust & Safety journey", + "description": "Moderating listings, investigating sellers and resolving disputes.", + "x": 0, + "y": 3204, + "width": 920, + "height": 330, + "collapsed": false + }, { "id": "mockup-listing-review-queue", "type": "mockup", "label": "Listing Review Queue", "description": "Trust & Safety queue sorted by time left on the review SLA. Rejecting a listing or suspending a seller removes it from search within {{SUPPRESSION_MINUTES}} minutes.", - "x": 320, - "y": 2084, + "parentId": "flow-trust-safety", + "x": 30, + "y": 110, "width": 220, "height": 190, "collapsed": false, @@ -442,17 +603,32 @@ "wireframe": "\n \n \n \n Trust & Safety\n Disputes\n Sellers\n Listings (14)\n \n \n \n \n \n \n \n \n 3 h left\n \n \n \n \n \n \n 7 h left\n \n \n \n \n \n \n 11 h left\n \n \n \n \n \n \n 19 h left\n \n \n \n \n \n \n 22 h left\n \n \n \n Mid-century armchair\n €180 · seller VintageCo\n \n New seller\n Policy checks\n \n \n Photos show the actual item\n \n \n Price and description are consistent\n \n No prohibited category or keywords\n Suspending a seller removes all their listings from search.\n \n Reject…\n \n Suspend\n \n Approve\n" }, { - "id": "mockup-order-dispute", + "id": "mockup-seller-investigation", "type": "mockup", - "label": "Order & Buyer Protection", - "description": "Buyer view of an order: funds stay held for {{DISPUTE_WINDOW_DAYS}} days after delivery, and opening a dispute freezes the payout.", - "x": 640, - "y": 2084, + "label": "Seller Investigation", + "description": "Suspending a seller removes every listing from search within {{SUPPRESSION_MINUTES}} minutes.", + "parentId": "flow-trust-safety", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, - "screen": "/orders/:id", - "wireframe": "\n \n \n \n Marketplace\n Account\n Orders\n Search\n Order #58213\n Sold by VintageCo · delivered 2 Apr\n \n \n \n \n \n €180.00\n Where your money is\n \n Paid ✓\n \n Delivered ✓\n \n Held for buyer protection\n \n Released to seller\n Buyer protection window\n 3 of 7 days\n \n \n Something wrong? Opening a dispute freezes the payout until it is resolved.\n \n Open a dispute\n \n Confirm all good\n" + "screen": "/trust/sellers/:id", + "wireframe": "\n \n \n \n Trust & Safety\n Disputes\n Sellers\n Listings (14)\n ← Listing review\n \n VintageCo\n Seller since 2023 · 212 sales · 3 reports this week\n \n Open reports\n 3\n \n Live listings\n 23\n \n Dispute rate\n 4.1%\n Reports\n \n \n counterfeit\n \n \n \n misdescribed\n \n \n Suspending hides all 23 listings from search within 5 minutes.\n \n Warn\n \n Suspend seller\n" + }, + { + "id": "mockup-ts-dispute-case", + "type": "mockup", + "label": "Dispute Case", + "description": "Evidence from both sides; the payout stays frozen until a resolution is chosen.", + "parentId": "flow-trust-safety", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/trust/disputes/:id", + "wireframe": "\n \n \n \n Trust & Safety\n Disputes (4)\n Sellers\n Listings\n Dispute DSP-58213\n \n payout frozen\n Buyer: item not as described · €180.00 · opened day 3 of 7\n \n Buyer\n \n \n \n \n \n \n \n \n \n \n Seller\n \n \n \n \n Resolution\n \n Refund buyer\n \n Partial refund\n \n Release to seller\n" } ], "relations": [ @@ -614,6 +790,121 @@ "targetId": "trust-safety-console", "relationType": "constrains" }, + { + "id": "mockup-mp-search--order-checkout-service", + "sourceId": "mockup-mp-search", + "targetId": "order-checkout-service", + "relationType": "presented-by" + }, + { + "id": "mockup-mp-search--mockup-mp-listing", + "sourceId": "mockup-mp-search", + "targetId": "mockup-mp-listing", + "relationType": "navigates-to", + "label": "Open listing" + }, + { + "id": "mockup-mp-listing--order-checkout-service", + "sourceId": "mockup-mp-listing", + "targetId": "order-checkout-service", + "relationType": "presented-by" + }, + { + "id": "mockup-mp-listing--mockup-mp-checkout", + "sourceId": "mockup-mp-listing", + "targetId": "mockup-mp-checkout", + "relationType": "navigates-to", + "label": "Buy now" + }, + { + "id": "mockup-mp-checkout--req-dispute-window-hold", + "sourceId": "mockup-mp-checkout", + "targetId": "req-dispute-window-hold", + "relationType": "illustrates" + }, + { + "id": "mockup-mp-checkout--order-checkout-service", + "sourceId": "mockup-mp-checkout", + "targetId": "order-checkout-service", + "relationType": "presented-by" + }, + { + "id": "mockup-mp-checkout--mockup-order-dispute", + "sourceId": "mockup-mp-checkout", + "targetId": "mockup-order-dispute", + "relationType": "navigates-to", + "label": "Pay" + }, + { + "id": "mockup-order-dispute--req-dispute-window-hold", + "sourceId": "mockup-order-dispute", + "targetId": "req-dispute-window-hold", + "relationType": "illustrates" + }, + { + "id": "mockup-order-dispute--order-checkout-service", + "sourceId": "mockup-order-dispute", + "targetId": "order-checkout-service", + "relationType": "presented-by" + }, + { + "id": "mockup-order-dispute--mockup-mp-open-dispute", + "sourceId": "mockup-order-dispute", + "targetId": "mockup-mp-open-dispute", + "relationType": "navigates-to", + "label": "Open a dispute" + }, + { + "id": "mockup-mp-open-dispute--req-dispute-window-hold", + "sourceId": "mockup-mp-open-dispute", + "targetId": "req-dispute-window-hold", + "relationType": "illustrates" + }, + { + "id": "mockup-mp-open-dispute--order-checkout-service", + "sourceId": "mockup-mp-open-dispute", + "targetId": "order-checkout-service", + "relationType": "presented-by" + }, + { + "id": "mockup-mp-open-dispute--mockup-mp-dispute-status", + "sourceId": "mockup-mp-open-dispute", + "targetId": "mockup-mp-dispute-status", + "relationType": "navigates-to", + "label": "Open dispute" + }, + { + "id": "mockup-mp-dispute-status--req-dispute-window-hold", + "sourceId": "mockup-mp-dispute-status", + "targetId": "req-dispute-window-hold", + "relationType": "illustrates" + }, + { + "id": "mockup-mp-dispute-status--order-checkout-service", + "sourceId": "mockup-mp-dispute-status", + "targetId": "order-checkout-service", + "relationType": "presented-by" + }, + { + "id": "mockup-seller-dashboard--listings-service", + "sourceId": "mockup-seller-dashboard", + "targetId": "listings-service", + "relationType": "presented-by" + }, + { + "id": "mockup-seller-dashboard--mockup-new-listing", + "sourceId": "mockup-seller-dashboard", + "targetId": "mockup-new-listing", + "relationType": "navigates-to", + "label": "New listing" + }, + { + "id": "mockup-seller-dashboard--mockup-seller-payouts", + "sourceId": "mockup-seller-dashboard", + "targetId": "mockup-seller-payouts", + "relationType": "navigates-to", + "label": "Payouts" + }, { "id": "mockup-new-listing--req-listing-moderation", "sourceId": "mockup-new-listing", @@ -626,6 +917,50 @@ "targetId": "listings-service", "relationType": "presented-by" }, + { + "id": "mockup-new-listing--mockup-my-listings", + "sourceId": "mockup-new-listing", + "targetId": "mockup-my-listings", + "relationType": "navigates-to", + "label": "Submit for review" + }, + { + "id": "mockup-my-listings--req-listing-moderation", + "sourceId": "mockup-my-listings", + "targetId": "req-listing-moderation", + "relationType": "illustrates" + }, + { + "id": "mockup-my-listings--listings-service", + "sourceId": "mockup-my-listings", + "targetId": "listings-service", + "relationType": "presented-by" + }, + { + "id": "mockup-my-listings--mockup-new-listing", + "sourceId": "mockup-my-listings", + "targetId": "mockup-new-listing", + "relationType": "navigates-to", + "label": "Edit & resubmit" + }, + { + "id": "mockup-seller-payouts--req-commission-accuracy", + "sourceId": "mockup-seller-payouts", + "targetId": "req-commission-accuracy", + "relationType": "illustrates" + }, + { + "id": "mockup-seller-payouts--req-dispute-window-hold", + "sourceId": "mockup-seller-payouts", + "targetId": "req-dispute-window-hold", + "relationType": "illustrates" + }, + { + "id": "mockup-seller-payouts--payout-worker", + "sourceId": "mockup-seller-payouts", + "targetId": "payout-worker", + "relationType": "presented-by" + }, { "id": "mockup-listing-review-queue--req-listing-moderation", "sourceId": "mockup-listing-review-queue", @@ -645,15 +980,48 @@ "relationType": "presented-by" }, { - "id": "mockup-order-dispute--req-dispute-window-hold", - "sourceId": "mockup-order-dispute", + "id": "mockup-listing-review-queue--mockup-seller-investigation", + "sourceId": "mockup-listing-review-queue", + "targetId": "mockup-seller-investigation", + "relationType": "navigates-to", + "label": "Suspend" + }, + { + "id": "mockup-seller-investigation--req-delisting-suppression", + "sourceId": "mockup-seller-investigation", + "targetId": "req-delisting-suppression", + "relationType": "illustrates" + }, + { + "id": "mockup-seller-investigation--trust-safety-console", + "sourceId": "mockup-seller-investigation", + "targetId": "trust-safety-console", + "relationType": "presented-by" + }, + { + "id": "mockup-seller-investigation--mockup-ts-dispute-case", + "sourceId": "mockup-seller-investigation", + "targetId": "mockup-ts-dispute-case", + "relationType": "navigates-to", + "label": "Disputes" + }, + { + "id": "mockup-seller-investigation--mockup-listing-review-queue", + "sourceId": "mockup-seller-investigation", + "targetId": "mockup-listing-review-queue", + "relationType": "navigates-to", + "label": "Suspend seller" + }, + { + "id": "mockup-ts-dispute-case--req-dispute-window-hold", + "sourceId": "mockup-ts-dispute-case", "targetId": "req-dispute-window-hold", "relationType": "illustrates" }, { - "id": "mockup-order-dispute--order-checkout-service", - "sourceId": "mockup-order-dispute", - "targetId": "order-checkout-service", + "id": "mockup-ts-dispute-case--trust-safety-console", + "sourceId": "mockup-ts-dispute-case", + "targetId": "trust-safety-console", "relationType": "presented-by" } ], diff --git a/hub/blueprint/bp-realtime-collaboration.radical b/hub/blueprint/bp-realtime-collaboration.radical index 571ab96..8acd379 100644 --- a/hub/blueprint/bp-realtime-collaboration.radical +++ b/hub/blueprint/bp-realtime-collaboration.radical @@ -358,13 +358,39 @@ "consequences": "Gateway instances scale and restart freely with no handoff logic, but every operation takes an extra hop through the bus, adding a small amount of latency to the common case.", "alternatives": "1. Sticky sessions routing all of a document's clients to one gateway instance — lower latency per operation, but that instance becomes a single point of failure and a hot-spot for popular documents. 2. Shared broadcast bus (chosen). 3. Peer-to-peer mesh between clients — no server bottleneck, but NAT traversal and offline persistence become much harder to get right." }, + { + "id": "flow-collaborator", + "type": "group", + "label": "Collaborator journey", + "description": "Editing together, offline and back, and going back in time.", + "x": 0, + "y": 2084, + "width": 1560, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-doc-list", + "type": "mockup", + "label": "Documents", + "description": "Workspace documents with who is editing them right now.", + "parentId": "flow-collaborator", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/docs", + "wireframe": "\n \n \n \n Workspace\n Settings\n Members\n Docs\n Documents\n \n + New doc\n \n Search documents\n \n Name\n Editing now\n Updated\n \n \n Q3 Planning\n \n 2 min ago\n \n Launch checklist\n \n 1 h ago\n \n \n \n yesterday\n \n \n \n last week\n \n \n \n last week\n \n \n \n \n \n" + }, { "id": "mockup-document-editor", "type": "mockup", "label": "Document Editor", "description": "Everyone edits a local replica: changes apply instantly, keep working offline (up to {{OFFLINE_GRACE_HOURS}} h) and merge on reconnect; presence shows up to {{MAX_DOC_COLLABORATORS}} collaborators.", - "x": 0, - "y": 2084, + "parentId": "flow-collaborator", + "x": 350, + "y": 110, "width": 220, "height": 190, "collapsed": false, @@ -372,30 +398,113 @@ "wireframe": "\n \n \n \n Q3 Planning\n \n \n \n \n +3\n \n Share\n \n \n \n \n \n \n \n \n \n Offline — edits saved locally\n Q3 Planning\n \n \n \n Goals\n \n \n \n \n Ana\n Risks\n \n \n \n History\n" }, { - "id": "mockup-share-dialog", + "id": "mockup-reconnect-merge", "type": "mockup", - "label": "Share & Access", - "description": "Workspace admins manage who can open the document; removing someone disconnects their live session instead of waiting for it to expire.", - "x": 320, - "y": 2084, + "label": "Back Online — Merged", + "description": "On reconnect the offline operation log is replayed and merged automatically — no conflict dialogs, no lost edits.", + "parentId": "flow-collaborator", + "x": 670, + "y": 110, "width": 220, "height": 190, "collapsed": false, - "screen": "/d/:docId/share", - "wireframe": "\n \n \n \n Share “Q3 Planning”\n \n Add people or groups\n \n Invite\n \n \n \n Owner\n \n \n \n \n Can edit\n \n Remove\n \n \n \n \n Can edit\n \n Remove\n \n \n \n \n Can view\n \n Remove\n Link access\n \n Only people in the workspace\n \n Removing someone disconnects their open session immediately.\n" + "screen": "/d/:docId", + "wireframe": "\n \n \n \n Q3 Planning\n \n \n \n \n \n Share\n \n Back online — 37 offline edits merged with 12 from others. Nothing was lost.\n Q3 Planning\n \n \n \n \n \n yours\n \n \n Ben\n \n \n \n Show what changed while I was offline\n" }, { "id": "mockup-version-history", "type": "mockup", "label": "Version History", "description": "Browse and restore any earlier state of the document, reconstructed from snapshots plus the operation log.", - "x": 640, - "y": 2084, + "parentId": "flow-collaborator", + "x": 990, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/d/:docId/history", "wireframe": "\n \n \n \n Q3 Planning — Version history\n Close\n Q3 Planning\n \n \n \n \n \n \n \n \n \n \n \n \n Today 14:02\n Ana, Ben\n \n Today 11:40\n Ben\n Yesterday 17:15\n Ana, Chris, +2\n Yesterday 09:03\n Chris\n 2 Apr 16:48\n Ana\n \n Restore this version\n" + }, + { + "id": "mockup-restore-version", + "type": "mockup", + "label": "Restore Version", + "description": "Restoring an older state is itself a new version, reconstructed from snapshot plus operation log.", + "parentId": "flow-collaborator", + "x": 1310, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/d/:docId/history/:version", + "wireframe": "\n \n \n \n Restore this version?\n Today 11:40 — edited by Ben\n The current document is kept as a new version,\n so restoring can itself be undone.\n \n Notify the 6 people editing now\n \n Cancel\n \n Restore\n" + }, + { + "id": "flow-workspace-admin", + "type": "group", + "label": "Workspace admin journey", + "description": "Sharing documents and removing people who leave.", + "x": 0, + "y": 2494, + "width": 1240, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-share-dialog", + "type": "mockup", + "label": "Share & Access", + "description": "Workspace admins manage who can open the document; removing someone disconnects their live session instead of waiting for it to expire.", + "parentId": "flow-workspace-admin", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/d/:docId/share", + "wireframe": "\n \n \n \n Share “Q3 Planning”\n \n Add people or groups\n \n Invite\n \n \n \n Owner\n \n \n \n \n Can edit\n \n Remove\n \n \n \n \n Can edit\n \n Remove\n \n \n \n \n Can view\n \n Remove\n Link access\n \n Only people in the workspace\n \n Removing someone disconnects their open session immediately.\n" + }, + { + "id": "mockup-workspace-members", + "type": "mockup", + "label": "Workspace Members", + "description": "Everyone with access to the workspace and their role.", + "parentId": "flow-workspace-admin", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/settings/members", + "wireframe": "\n \n \n \n Workspace\n Settings\n Members\n Docs\n Members\n \n Invite\n \n Member\n Role\n Last active\n \n \n Ana Ruiz\n Admin\n now\n \n \n Ben Okafor\n Member\n now\n ⋯\n \n \n Chris Lee\n Member\n 2 d ago\n ⋯\n \n Dana Kim\n Guest\n 1 h ago\n ⋯\n \n \n Member\n \n ⋯\n Removing a member ends their open sessions right away.\n" + }, + { + "id": "mockup-remove-member", + "type": "mockup", + "label": "Remove Member", + "description": "Removal disconnects live sessions immediately instead of waiting for tokens to expire.", + "parentId": "flow-workspace-admin", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/settings/members/:id/remove", + "wireframe": "\n \n \n \n Remove Chris Lee?\n Chris is editing 2 documents right now.\n \n \n Disconnect open sessions immediately\n \n \n Transfer ownership of 5 documents to me\n Further edits from Chris are rejected from now on.\n \n Cancel\n \n Remove\n" + }, + { + "id": "mockup-access-revoked", + "type": "mockup", + "label": "Access Removed", + "description": "What the removed collaborator sees: the session is closed and further operations are rejected.", + "parentId": "flow-workspace-admin", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/d/:docId", + "wireframe": "\n \n \n \n Q3 Planning\n \n \n You no longer have access\n An admin removed you from this document.\n Unsent changes were not saved to it.\n \n Back to workspace\n" } ], "relations": [ @@ -536,6 +645,19 @@ "targetId": "adr-stateless-gateway-fleet", "relationType": "traces-to" }, + { + "id": "mockup-doc-list--editor-client", + "sourceId": "mockup-doc-list", + "targetId": "editor-client", + "relationType": "presented-by" + }, + { + "id": "mockup-doc-list--mockup-document-editor", + "sourceId": "mockup-doc-list", + "targetId": "mockup-document-editor", + "relationType": "navigates-to", + "label": "Open" + }, { "id": "mockup-document-editor--req-offline-merge", "sourceId": "mockup-document-editor", @@ -554,6 +676,13 @@ "targetId": "editor-client", "relationType": "presented-by" }, + { + "id": "mockup-document-editor--mockup-reconnect-merge", + "sourceId": "mockup-document-editor", + "targetId": "mockup-reconnect-merge", + "relationType": "navigates-to", + "label": "Reconnect" + }, { "id": "mockup-document-editor--mockup-share-dialog", "sourceId": "mockup-document-editor", @@ -569,14 +698,14 @@ "label": "History" }, { - "id": "mockup-share-dialog--req-access-revocation", - "sourceId": "mockup-share-dialog", - "targetId": "req-access-revocation", + "id": "mockup-reconnect-merge--req-offline-merge", + "sourceId": "mockup-reconnect-merge", + "targetId": "req-offline-merge", "relationType": "illustrates" }, { - "id": "mockup-share-dialog--editor-client", - "sourceId": "mockup-share-dialog", + "id": "mockup-reconnect-merge--editor-client", + "sourceId": "mockup-reconnect-merge", "targetId": "editor-client", "relationType": "presented-by" }, @@ -591,6 +720,108 @@ "sourceId": "mockup-version-history", "targetId": "editor-client", "relationType": "presented-by" + }, + { + "id": "mockup-version-history--mockup-restore-version", + "sourceId": "mockup-version-history", + "targetId": "mockup-restore-version", + "relationType": "navigates-to", + "label": "Restore this version" + }, + { + "id": "mockup-restore-version--req-snapshot-recovery", + "sourceId": "mockup-restore-version", + "targetId": "req-snapshot-recovery", + "relationType": "illustrates" + }, + { + "id": "mockup-restore-version--editor-client", + "sourceId": "mockup-restore-version", + "targetId": "editor-client", + "relationType": "presented-by" + }, + { + "id": "mockup-restore-version--mockup-document-editor", + "sourceId": "mockup-restore-version", + "targetId": "mockup-document-editor", + "relationType": "navigates-to", + "label": "Restore" + }, + { + "id": "mockup-share-dialog--req-access-revocation", + "sourceId": "mockup-share-dialog", + "targetId": "req-access-revocation", + "relationType": "illustrates" + }, + { + "id": "mockup-share-dialog--editor-client", + "sourceId": "mockup-share-dialog", + "targetId": "editor-client", + "relationType": "presented-by" + }, + { + "id": "mockup-share-dialog--mockup-workspace-members", + "sourceId": "mockup-share-dialog", + "targetId": "mockup-workspace-members", + "relationType": "navigates-to", + "label": "Manage members" + }, + { + "id": "mockup-workspace-members--req-access-revocation", + "sourceId": "mockup-workspace-members", + "targetId": "req-access-revocation", + "relationType": "illustrates" + }, + { + "id": "mockup-workspace-members--editor-client", + "sourceId": "mockup-workspace-members", + "targetId": "editor-client", + "relationType": "presented-by" + }, + { + "id": "mockup-workspace-members--mockup-remove-member", + "sourceId": "mockup-workspace-members", + "targetId": "mockup-remove-member", + "relationType": "navigates-to", + "label": "Remove" + }, + { + "id": "mockup-remove-member--req-access-revocation", + "sourceId": "mockup-remove-member", + "targetId": "req-access-revocation", + "relationType": "illustrates" + }, + { + "id": "mockup-remove-member--editor-client", + "sourceId": "mockup-remove-member", + "targetId": "editor-client", + "relationType": "presented-by" + }, + { + "id": "mockup-remove-member--mockup-access-revoked", + "sourceId": "mockup-remove-member", + "targetId": "mockup-access-revoked", + "relationType": "navigates-to", + "label": "Remove" + }, + { + "id": "mockup-access-revoked--req-access-revocation", + "sourceId": "mockup-access-revoked", + "targetId": "req-access-revocation", + "relationType": "illustrates" + }, + { + "id": "mockup-access-revoked--editor-client", + "sourceId": "mockup-access-revoked", + "targetId": "editor-client", + "relationType": "presented-by" + }, + { + "id": "mockup-access-revoked--mockup-doc-list", + "sourceId": "mockup-access-revoked", + "targetId": "mockup-doc-list", + "relationType": "navigates-to", + "label": "Back to workspace" } ], "sequences": [ diff --git a/hub/blueprint/bp-saas-b2b-platform.radical b/hub/blueprint/bp-saas-b2b-platform.radical index 6f84d19..6396024 100644 --- a/hub/blueprint/bp-saas-b2b-platform.radical +++ b/hub/blueprint/bp-saas-b2b-platform.radical @@ -441,44 +441,217 @@ "consequences": "A tenant revoking a user at their IdP takes effect on the token's next short expiry, not instantly, which is an accepted trade-off; token issuance adds one extra hop to first login.", "alternatives": "Storing IdP session cookies directly was rejected — it would tie the platform's session lifetime to each IdP's own cookie semantics and leak IdP-specific state into the app tier." }, + { + "id": "flow-onboarding", + "type": "group", + "label": "Onboarding journey", + "description": "From sign-up to a working, provisioned workspace.", + "x": 0, + "y": 2324, + "width": 920, + "height": 330, + "collapsed": false + }, { "id": "mockup-signup", "type": "mockup", "label": "Sign Up", "description": "Self-serve sign-up; the new tenant gets an isolated, ready-to-use workspace within {{PROVISION_MINUTES}} minutes.", - "x": 0, - "y": 2324, + "parentId": "flow-onboarding", + "x": 30, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/signup", "wireframe": "\n \n \n \n \n Start your free trial\n Work e-mail\n \n jane@acme.com\n Company\n \n Acme Inc.\n Workspace URL\n \n acme.app.example\n \n Create workspace\n or\n \n Sign up with SSO\n Your workspace is ready in a couple of minutes.\n" }, + { + "id": "mockup-provisioning", + "type": "mockup", + "label": "Provisioning", + "description": "Provisioning progress; an isolated workspace is ready within {{PROVISION_MINUTES}} minutes.", + "parentId": "flow-onboarding", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/setup", + "wireframe": "\n \n \n \n Setting up acme.app.example\n \n \n \n \n Workspace and database schema\n \n \n Default roles and your admin account\n \n Sample project\n Usually ready in under 2 minutes.\n We'll e-mail you when it's done.\n" + }, + { + "id": "mockup-invite-team", + "type": "mockup", + "label": "Invite Your Team", + "description": "Last onboarding step; invites land in the new tenant only.", + "parentId": "flow-onboarding", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/setup/invite", + "wireframe": "\n \n \n \n Invite your team\n Step 2 of 2\n \n ben@acme.com\n \n chris@acme.com\n \n Add another e-mail…\n Default role\n \n Member\n \n \n Send invites\n Skip for now\n" + }, + { + "id": "flow-tenant-user", + "type": "group", + "label": "Tenant user journey", + "description": "Daily work inside the tenant, and what happens when billing lapses.", + "x": 0, + "y": 2734, + "width": 920, + "height": 330, + "collapsed": false + }, { "id": "mockup-workspace-dashboard", "type": "mockup", "label": "Workspace Dashboard", - "description": "Tenant home. When a payment fails the workspace turns read-only — data stays visible, writes are blocked — until billing is fixed.", - "x": 320, - "y": 2324, + "description": "Tenant home; every request carries the resolved tenant context.", + "parentId": "flow-tenant-user", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/", + "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n Welcome back, Jane\n \n Active projects\n 12\n \n Members\n 48\n \n API calls this month\n 812k\n Projects\n \n + New project\n \n Name\n Owner\n Updated\n \n \n Website relaunch\n \n 2 h ago\n \n \n \n yesterday\n \n \n \n 3 days ago\n \n \n \n last week\n \n \n \n last week\n" + }, + { + "id": "mockup-project", + "type": "mockup", + "label": "Project", + "description": "Project board; all data is scoped by tenant id — nothing from other tenants is reachable.", + "parentId": "flow-tenant-user", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/projects/:id", + "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n ← Projects\n Website relaunch\n acme.app.example/projects/website-relaunch\n Tasks\n \n Files\n Activity\n \n \n To do\n \n \n \n \n \n \n \n \n \n \n In progress\n \n \n \n \n \n \n \n Done\n \n \n \n \n \n \n" + }, + { + "id": "mockup-read-only", + "type": "mockup", + "label": "Read-Only Workspace", + "description": "After a failed payment the workspace stays readable but writes are blocked until billing is fixed.", + "parentId": "flow-tenant-user", + "x": 670, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/", "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n \n Payment failed — the workspace is read-only until billing is updated.\n Update billing\n \n Active projects\n 12\n \n Members\n 48\n \n API calls this month\n 812k\n Projects\n \n + New project\n \n Name\n Owner\n Updated\n \n \n \n 2 h ago\n \n \n \n yesterday\n \n \n \n 3 days ago\n \n \n \n last week\n \n \n \n last week\n" }, + { + "id": "flow-tenant-admin", + "type": "group", + "label": "Tenant admin journey", + "description": "Billing, SSO and user management for one organisation.", + "x": 0, + "y": 3144, + "width": 1240, + "height": 330, + "collapsed": false + }, { "id": "mockup-billing-usage", "type": "mockup", "label": "Billing & Usage", "description": "Tenant admin view of plan, metered usage and invoices; usage figures are recorded exactly once and reconciled against source logs.", - "x": 640, - "y": 2324, + "parentId": "flow-tenant-admin", + "x": 30, + "y": 110, "width": 220, "height": 190, "collapsed": false, "screen": "/settings/billing", - "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n \n Profile\n Users\n Single sign-on\n \n Billing & usage\n Audit log\n Billing & usage\n \n Business plan\n €49 per seat / month · 48 seats\n \n Change plan\n API calls\n 812k / 1M\n \n \n Storage\n 38 / 100 GB\n \n \n Usage is metered per event and reconciled daily against source logs.\n \n Invoice\n Amount\n Status\n \n Mar 2026\n €2,352.00\n \n payment failed\n \n Feb 2026\n €2,254.00\n \n paid\n \n Update payment method\n" + "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n \n Profile\n Users & roles\n Single sign-on\n \n Billing & usage\n Audit log\n Billing & usage\n \n Business plan\n €49 per seat / month · 48 seats\n \n Change plan\n API calls\n 812k / 1M\n \n \n Storage\n 38 / 100 GB\n \n \n Usage is metered per event and reconciled daily against source logs.\n \n Invoice\n Amount\n Status\n \n Mar 2026\n €2,352.00\n \n payment failed\n \n Feb 2026\n €2,254.00\n \n paid\n \n Update payment method\n" + }, + { + "id": "mockup-update-payment", + "type": "mockup", + "label": "Update Payment Method", + "description": "New card; the failed invoice is retried immediately and write access comes back on success.", + "parentId": "flow-tenant-admin", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/settings/billing/payment", + "wireframe": "\n \n \n \n Update payment method\n Card number\n \n •••• •••• •••• ••••\n Expiry\n \n MM / YY\n CVC\n \n •••\n We retry the failed invoice right away; write access\n returns as soon as the payment succeeds.\n \n Cancel\n \n Save & pay €2,352\n" + }, + { + "id": "mockup-sso-settings", + "type": "mockup", + "label": "Single Sign-On", + "description": "SAML/OIDC federation with the tenant's own identity provider.", + "parentId": "flow-tenant-admin", + "x": 670, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/settings/sso", + "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n \n Profile\n Users & roles\n \n Single sign-on\n Billing & usage\n Audit log\n Single sign-on\n Identity provider\n \n Okta (SAML 2.0)\n \n SSO URL\n \n https://acme.okta.com/app/…/sso/saml\n Entity ID\n \n http://www.okta.com/exk…\n X.509 certificate\n \n -----BEGIN CERTIFICATE-----…\n \n \n Require SSO for all members\n \n Test connection\n \n Save\n" + }, + { + "id": "mockup-users-roles", + "type": "mockup", + "label": "Users & Roles", + "description": "Seats, roles and how each user signs in.", + "parentId": "flow-tenant-admin", + "x": 990, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/settings/users", + "wireframe": "\n \n \n \n Acme ▾\n Settings\n Reports\n Projects\n \n Profile\n \n Users & roles\n Single sign-on\n Billing & usage\n Audit log\n Users & roles\n \n Invite\n \n User\n Role\n Via\n \n Jane Doe\n Admin\n SSO\n \n Ben Okafor\n Member\n SSO\n \n Chris Lee\n Member\n invite\n \n \n Viewer\n SSO\n \n \n Member\n SSO\n \n \n Member\n SSO\n 48 seats in use of 50\n" + }, + { + "id": "flow-platform-ops", + "type": "group", + "label": "Platform ops journey", + "description": "Watching tenant health across the pooled tier.", + "x": 0, + "y": 3554, + "width": 600, + "height": 330, + "collapsed": false + }, + { + "id": "mockup-tenant-list", + "type": "mockup", + "label": "Tenants", + "description": "All tenants with plan, tier and share of pooled load.", + "parentId": "flow-platform-ops", + "x": 30, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/admin/tenants", + "wireframe": "\n \n \n \n Admin Console\n Audit\n Incidents\n Tenants\n Tenants\n \n Search tenants\n \n Tenant\n Plan\n Tier\n Load share\n Status\n \n Acme Inc.\n Business\n pooled\n 3.1%\n \n ok\n \n Globex\n Enterprise\n silo\n —\n \n ok\n \n \n Initech\n Business\n pooled\n 21.4%\n \n hot\n \n \n Starter\n pooled\n 0.4%\n \n ok\n \n \n Business\n pooled\n 1.2%\n \n ok\n" + }, + { + "id": "mockup-tenant-detail", + "type": "mockup", + "label": "Tenant Detail", + "description": "Per-tenant health against the {{MAX_TENANT_SHARE_PCT}}% noisy-neighbour limit; impersonation is audited.", + "parentId": "flow-platform-ops", + "x": 350, + "y": 110, + "width": 220, + "height": 190, + "collapsed": false, + "screen": "/admin/tenants/:id", + "wireframe": "\n \n \n \n Admin Console\n Audit\n Incidents\n Tenants\n ← Tenants\n Initech\n \n pooled tier\n \n Share of pooled load\n 21.4%\n \n p95 latency\n 310 ms\n \n Seats\n 420\n Load share vs 20% limit\n 21.4% / 20%\n \n \n Over the noisy-neighbour limit — consider moving to a silo.\n \n Impersonation is logged to the tenant's audit log\n and requires a support ticket id.\n \n Impersonate…\n \n Migrate to silo\n" } ], "relations": [ @@ -614,18 +787,44 @@ "relationType": "presented-by" }, { - "id": "mockup-signup--mockup-workspace-dashboard", + "id": "mockup-signup--mockup-provisioning", "sourceId": "mockup-signup", - "targetId": "mockup-workspace-dashboard", + "targetId": "mockup-provisioning", "relationType": "navigates-to", "label": "Create workspace" }, { - "id": "mockup-workspace-dashboard--req-subscription-enforcement", - "sourceId": "mockup-workspace-dashboard", - "targetId": "req-subscription-enforcement", + "id": "mockup-provisioning--req-tenant-provisioning-time", + "sourceId": "mockup-provisioning", + "targetId": "req-tenant-provisioning-time", "relationType": "illustrates" }, + { + "id": "mockup-provisioning--webapp", + "sourceId": "mockup-provisioning", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-provisioning--mockup-invite-team", + "sourceId": "mockup-provisioning", + "targetId": "mockup-invite-team", + "relationType": "navigates-to", + "label": "Ready" + }, + { + "id": "mockup-invite-team--webapp", + "sourceId": "mockup-invite-team", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-invite-team--mockup-workspace-dashboard", + "sourceId": "mockup-invite-team", + "targetId": "mockup-workspace-dashboard", + "relationType": "navigates-to", + "label": "Send invites" + }, { "id": "mockup-workspace-dashboard--webapp", "sourceId": "mockup-workspace-dashboard", @@ -633,8 +832,46 @@ "relationType": "presented-by" }, { - "id": "mockup-workspace-dashboard--mockup-billing-usage", + "id": "mockup-workspace-dashboard--mockup-project", + "sourceId": "mockup-workspace-dashboard", + "targetId": "mockup-project", + "relationType": "navigates-to", + "label": "Open project" + }, + { + "id": "mockup-workspace-dashboard--mockup-read-only", "sourceId": "mockup-workspace-dashboard", + "targetId": "mockup-read-only", + "relationType": "navigates-to", + "label": "Payment fails" + }, + { + "id": "mockup-project--req-tenant-isolation", + "sourceId": "mockup-project", + "targetId": "req-tenant-isolation", + "relationType": "illustrates" + }, + { + "id": "mockup-project--webapp", + "sourceId": "mockup-project", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-read-only--req-subscription-enforcement", + "sourceId": "mockup-read-only", + "targetId": "req-subscription-enforcement", + "relationType": "illustrates" + }, + { + "id": "mockup-read-only--webapp", + "sourceId": "mockup-read-only", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-read-only--mockup-billing-usage", + "sourceId": "mockup-read-only", "targetId": "mockup-billing-usage", "relationType": "navigates-to", "label": "Update billing" @@ -656,6 +893,83 @@ "sourceId": "mockup-billing-usage", "targetId": "webapp", "relationType": "presented-by" + }, + { + "id": "mockup-billing-usage--mockup-update-payment", + "sourceId": "mockup-billing-usage", + "targetId": "mockup-update-payment", + "relationType": "navigates-to", + "label": "Update payment method" + }, + { + "id": "mockup-billing-usage--mockup-sso-settings", + "sourceId": "mockup-billing-usage", + "targetId": "mockup-sso-settings", + "relationType": "navigates-to", + "label": "Single sign-on" + }, + { + "id": "mockup-update-payment--req-subscription-enforcement", + "sourceId": "mockup-update-payment", + "targetId": "req-subscription-enforcement", + "relationType": "illustrates" + }, + { + "id": "mockup-update-payment--webapp", + "sourceId": "mockup-update-payment", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-update-payment--mockup-workspace-dashboard", + "sourceId": "mockup-update-payment", + "targetId": "mockup-workspace-dashboard", + "relationType": "navigates-to", + "label": "Save & pay" + }, + { + "id": "mockup-sso-settings--webapp", + "sourceId": "mockup-sso-settings", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-sso-settings--mockup-users-roles", + "sourceId": "mockup-sso-settings", + "targetId": "mockup-users-roles", + "relationType": "navigates-to", + "label": "Users & roles" + }, + { + "id": "mockup-users-roles--webapp", + "sourceId": "mockup-users-roles", + "targetId": "webapp", + "relationType": "presented-by" + }, + { + "id": "mockup-tenant-list--admin-console", + "sourceId": "mockup-tenant-list", + "targetId": "admin-console", + "relationType": "presented-by" + }, + { + "id": "mockup-tenant-list--mockup-tenant-detail", + "sourceId": "mockup-tenant-list", + "targetId": "mockup-tenant-detail", + "relationType": "navigates-to", + "label": "Open tenant" + }, + { + "id": "mockup-tenant-detail--req-tenant-isolation", + "sourceId": "mockup-tenant-detail", + "targetId": "req-tenant-isolation", + "relationType": "illustrates" + }, + { + "id": "mockup-tenant-detail--admin-console", + "sourceId": "mockup-tenant-detail", + "targetId": "admin-console", + "relationType": "presented-by" } ] } diff --git a/tests/hubCatalogue.test.ts b/tests/hubCatalogue.test.ts index 9f991f0..994d61b 100644 --- a/tests/hubCatalogue.test.ts +++ b/tests/hubCatalogue.test.ts @@ -4,15 +4,16 @@ * - every top-level hub//.radical is valid (hub block, ids, refs) * - index.json summaries carry what the cards / drop-target check need * - doc ↔ concept mapping round-trips - * - blueprint mockups are wired up with metamodel-valid relations and - * carry wireframes that are already sanitised + * - blueprint mockups form per-actor screen flows (flow groups linked by + * navigates-to), use metamodel-valid relations and carry wireframes that + * are already sanitised */ import { describe, it, expect } from 'vitest' import { resolve } from 'node:path' import { readCatalogue, validateCatalogue, buildIndex } from '../tools/hubCatalogue' import { conceptToDoc, docToConcept, summarize, type HubRadicalDoc } from '../src/renderer/src/hub/hubFormat' import { useDiagramStore } from '../src/renderer/src/store/diagramStore' -import { builtInGovernanceMetamodel } from '../src/renderer/src/types/metamodel' +import { builtInGovernanceMetamodel, isParentAllowed } from '../src/renderer/src/types/metamodel' import { sanitizeWireframeSvg } from '../src/renderer/src/ai/mockupWireframe' const HUB_DIR = resolve(__dirname, '../hub') @@ -89,12 +90,17 @@ describe('blueprint mockups', () => { const blueprints = readCatalogue(HUB_DIR).filter((e) => e.doc.hub.category === 'blueprint') const mm = builtInGovernanceMetamodel() - it.each(blueprints.map((e) => [e.file, e.doc] as const))('%s has wired-up mockups with sanitised wireframes', (_file, doc) => { + it.each(blueprints.map((e) => [e.file, e.doc] as const))('%s has screen flows with sanitised wireframes', (_file, doc) => { const byId = new Map(doc.nodes.map((n) => [String(n.id), n])) const mockups = doc.nodes.filter((n) => n.type === 'mockup') - expect(mockups.length).toBeGreaterThan(0) + expect(mockups.length).toBeGreaterThanOrEqual(8) + const inFlow = new Set((doc.relations ?? []).filter((r) => r.relationType === 'navigates-to').flatMap((r) => [r.sourceId, r.targetId])) for (const m of mockups) { + const flow = byId.get(String(m.parentId)) + expect(flow?.type, `${m.id}: not inside a flow group`).toBe('group') + expect(isParentAllowed(mm, 'mockup', 'group')).toBe(true) + expect(inFlow.has(String(m.id)), `${m.id}: not linked into its flow`).toBe(true) const wireframe = m.wireframe as string expect(typeof m.screen).toBe('string') expect(sanitizeWireframeSvg(wireframe)).toBe(wireframe)