Skip to content

feat(ui): Decision Flow — see your routing pipeline as one living diagram - #426

Open
prajjwalkumar17 wants to merge 42 commits into
mainfrom
feat/decision-flow-page
Open

prajjwalkumar17 wants to merge 42 commits into
mainfrom
feat/decision-flow-page

Conversation

@prajjwalkumar17

@prajjwalkumar17 prajjwalkumar17 commented Sep 7, 2026

Copy link
Copy Markdown
Member

What

A new read-only dashboard page, Routing → Decision Flow (/routing/flow), that shows a merchant how their payments actually move through the Decision Engine: what runs first, what runs next, what never runs at all, and how the pieces they configured compose.

New users land on the dashboard with five strategies to configure and no picture of how they interact (one activation slot shared by rules / volume splits / experiments; success-rate, elimination, cost and commitments layered on top; debit and preferred-gateway driven by the request). This page is that picture.

How it works

Your routing stack — a status board read live from the same endpoints Routing Hub already uses (/routing/list/active, /rule/get successRate|elimination, /merchant-account/…/features, the debit-routing flag). The active strategy shows its real saved name; off layers link to their configure pages; derived warnings surface config traps (empty slot, experiment holding the slot with the real-payments flag off, SR without elimination).

The journey of one payment — the 13 stages of the decide-gateway pipeline in true execution order, grouped into Entry / Experiment layer / Network branch / Candidates / Ordering / Decision & learning. Stage badges reflect configured state; clicking a stage expands what it does, when it runs, which page configures it, and the API behind it — including the merchant's actual rules (names, condition summaries, destinations) and split percentages.

Connector lanes — every connector the active strategy references is drawn as a continuous SVG ribbon flowing behind the stage cards: fanning out from the payment dot, lane thickness following volume-split shares, converging on the configured first choice when the strategy fully determines one (single / priority head). Measured with a ResizeObserver; draw-in animation respects prefers-reduced-motion.

Honest by construction

  • No simulation, no writes: what-if payments stay on the Decision Simulator. This page only mirrors saved configuration.
  • Anything payment-dependent (rule matching, eligibility outcomes, SR ordering) is described, never faked.
  • Empty slot → dashed ghost lanes + a warning that unfiltered lists go straight to scoring and ties break arbitrarily.
  • Fetch failures render as "couldn't load part of your configuration", not as false "Not set" facts (/rule/get 404s are treated as the legitimate not-configured state).

Verification

  • tsc + vite build clean; both themes checked in the dev server (empty-state merchant, expansions, lane canvas resize behavior).
  • Stage copy and data derivations were cross-checked against the backend (flow_new.rs pipeline order, ab_test interceptor flag semantics, externally-tagged Output serialization of default_selection, routing_algorithm_mapper slot semantics) and against existing frontend consumers (RoutingHubPage, euclid/summarize.ts). An adversarial multi-agent review over the diff produced 18 confirmed findings — all fixed in this commit (notably: default_selection shape unwrapping, SVG canvas height ratcheting after expand/collapse, ghost-lane dash pattern being overridden by the draw-in animation, SWR retry storms on expected 404s, and error states masquerading as "not configured").

Follow-ups (not in this PR)

  • Replay a real payment's /analytics/payment-audit trace onto the same lanes.
  • Optional backend niceties: an aggregate GET /routing/stack/{merchant_id}, and a read endpoint for the merchant_config PaymentFlow gates (SrBasedRouting / EliminationBasedRouting), which no API exposes today — the board approximates them the same way Routing Hub does.

Evidences

Screenshot 2026-09-08 at 14 14 07 Screenshot 2026-09-08 at 14 14 16 Screenshot 2026-09-08 at 14 14 23

Closes #427

prajjwalkumar17 and others added 3 commits September 7, 2026 19:56
…pipeline

New read-only page at /routing/flow (sidebar: Routing > Decision Flow) that
answers the question new merchants keep asking: what runs first, what runs
next, and what never runs at all.

- "Your routing stack" status board, read live from the same endpoints
  Routing Hub uses (/routing/list/active, /rule/get successRate and
  elimination, merchant features, debit-routing flag), with the active
  strategy named, off layers linking to their config pages, and derived
  warnings (empty slot, experiment holding the slot, SR without elimination).
- The pipeline rail: 13 stages in true decide-gateway execution order,
  grouped (Entry / Experiment / Network branch / Candidates / Ordering /
  Decision & learning). Each stage badges its configured state and expands
  inline with what it does, when it runs, and the API behind it — including
  the merchant's actual rules (name, conditions, destinations) and volume
  splits.
- Connector lanes: each connector the active strategy references is drawn
  as one continuous SVG ribbon behind the stage cards — fanning out from
  the payment, thickness following volume-split shares, converging on the
  configured first choice when the strategy determines one. Ghost lanes and
  honest empty states when nothing is configured; load failures render as
  "couldn't load" rather than as false "not set" facts.

No simulation and no writes: what-if payments stay on the Decision
Simulator; this page only mirrors saved configuration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unconfigured layers no longer render as greyed 'Not set' rows — the rail
draws just the pipeline this merchant has (always-run stages plus whatever
is configured), and whole groups disappear when empty. The routing-stack
board above remains the discovery surface: every layer stays listed there
with its state and a Configure link, and a stage joins the flow as soon as
its configuration lands (the reads revalidate on remount/focus, and the
feature/flag toggles mutate the shared SWR caches at write time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A merchant with nothing configured gets 404s from several of the reads; the
load-failure banner should only appear for genuine errors on any of them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@prajjwalkumar17 prajjwalkumar17 self-assigned this Sep 8, 2026
jagan-jaya
jagan-jaya previously approved these changes Sep 8, 2026
A merchant with no active strategy used to get grey 'connector A/B/C'
placeholders — technically honest, visually dead. Show a plausible example
set (razorpay / payu / stripe) instead: full lane colors, dashed strokes,
and an inline 'example set — activate a strategy to see yours' note so it
can never be mistaken for real configuration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prajjwalkumar17 and others added 2 commits September 8, 2026 15:49
…guration

Every stage's expansion prose is now computed from the merchant's actual
stack instead of fixed text. The Decision stage says who really wins —
best live score (+ cost promotion), the strategy's own order when scoring
is off, or an honest 'effectively arbitrary' when nothing is configured —
and carries a matching inline label. Baseline priority, health penalties,
cost, learning loop, eligibility, arrive and the strategy slot all reword
themselves the same way (e.g. the learning loop admits that nothing reads
reported outcomes when scoring and elimination are both off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each callable stage's expansion now carries a minimal curl — required
fields only — rebuilt from the live configuration: real connector names in
eligibleGatewayList, /routing/evaluate parameters that satisfy the
merchant's first rule, and sensible defaults when nothing is configured.
The no-config Decision copy now says plainly that the payment goes to the
integration's /decide-gateway fallback list (arbitrary pick within it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prajjwalkumar17 and others added 20 commits September 8, 2026 16:05
The eligibility check is driven by the server's pm_filters TOML, which no
API exposes — the expansion now shows a small excerpt of its real shape
(per connector + payment method, on country and currency, values from
config/development.toml) so the mechanism is concrete instead of abstract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on Flow

When a static strategy and success-rate scoring are both live, the payment
example switches to POST /routing/hybrid — static half carrying the rule
parameters, dynamic half without eligibleGatewayList so the strategy's
output feeds scoring — and the two stages tag themselves 'step 1 · hard
filter' and 'step 2 · re-rank'. Every stage card now carries a small
top-right tag naming its operation kind (input, traffic split, branch,
hard filter, weighted pick, ordering, re-rank, demote, override, pick
winner, feedback), tinted by family.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The hub answers 'what is active'; the flow is its picture — they belong on
one screen. The hub gets the same URL-backed underline tabs the Multi
Objective page uses (Strategies | Decision Flow · New, ?tab=flow), the
standalone sidebar entry goes away, and /routing/flow redirects to
/routing?tab=flow so existing links keep working. DecisionFlowPage becomes
DecisionFlowView, embedded with its own reads (SWR keys shared with the
hub, so nothing fetches twice).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The feature never shipped standalone, so there are no bookmarks to honor —
the hub tab (/routing?tab=flow) is the only address.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every ribbon now carries a subtle downstream flow pulse (dash cycle,
disabled under prefers-reduced-motion). With a volume split active, the
gap below the strategy shows each lane's real share as a percentage chip
and the flow speed encodes it — 60% traffic visibly moves faster than 10%.
The eligibility gap teaches the drop mechanic: the example set genuinely
loses its last lane there, while real strategies get a clearly-dashed
illustrative '✕ if not eligible' fork, since actual drops depend on the
payment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The page is educational, not analytics — so let it move. Small glowing
'payment' dots now travel each ribbon end-to-end on a loop (SMIL
animateMotion along the real lane paths), with travel speed and particle
count following volume-split shares; busy lanes visibly carry more
traffic. The payment-origin dot breathes, and the decided first choice
gets a pulsing winner dot at the converge. All of it sits behind
prefers-reduced-motion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A leg configured at 0% receives nothing, so its ribbon now ends at the
strategy with a '✕ 0% — no traffic' marker instead of flowing to the end —
no flow pulse, no particles, and the lanes below genuinely narrow to the
legs that carry traffic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Below success-rate scoring the lanes now periodically shuffle into a new
order with smoothly animated crossings — the ranking genuinely changes
payment to payment, and the continuous re-sort makes that visible instead
of implied. Implemented as a rebuilt shared path builder: the sort gap
curves each ribbon from its slot to an animated below-sort slot; a rotation
among the traffic-carrying lanes retargets every ~4.6s and tweens over
~950ms by mutating path data in place (so the draw-in never replays), then
refreshes the particle motion paths so payments ride the new order. Dead
lanes (0% legs, dropped examples) never move; disabled entirely under
prefers-reduced-motion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n move

The ordering gaps now form a staggered wave that travels down the page
every cycle: success-rate rotates the whole field, health penalties
sometimes demote one lane to the back (amber '▼ penalized' flash — demoted,
never removed, matching the real semantics), and cost optimization swaps
the leading pair when the cheaper gateway overtakes. Each ordering gap
carries its own slot state consumed sequentially by the shared path
builder, phases tween one after another so crossings cascade, and particles
refresh onto the final geometry after each wave. Dead lanes never move;
everything gates off under prefers-reduced-motion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eration

Reworked the wave so every stage's motion matches its tag. The hard filter
(eligibility) now sometimes removes a connector outright — lane cut at the
gap with a persistent '✕ not eligible' marker — and re-admits it a cycle
later ('↩ back'): across payments, different connectors genuinely drop
there. Health penalties likewise take a connector out for a cycle
('▼ penalized') and let it back in as scores recover, instead of
reordering. Only the two re-rank stages sort (success-rate rotates, cost
swaps the leaders). Payment particles hide while lanes re-shape and
re-enter on the new geometry, fixing dots drifting off-ribbon mid-wave.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail now ends the way it begins — with named chips. Below the Decision
stage each surviving lane carries its connector chip; after every wave the
chips glide to their final slots (leftmost = current leader) and hide while
their connector is filtered out or penalized. With a deterministic winner,
the converged lane is chip-named at the winner dot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n the break

Cuts no longer swap path geometry abruptly. A removed lane now retracts
smoothly to the exact break point (an animated stroke-dasharray mask over
pathLength-normalized paths, with the break fraction found by binary search
over real arc length) and regrows the same way when re-admitted; example
lanes recover their dash pattern after regrowing. The connector's
'✕ not eligible' / '▼ penalized' marker pops in timed to the retracting tip
reaching the gap, and the cut lane's flow pulse and payment particles fade
with it instead of floating on a hidden ribbon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A calmer cadence throughout: waves every ~9.5s with longer beats between
stages, sort crossings glide over 1.4s on an S-curve, lane cuts retract and
regrow over 1.3s with the break marker landing at ~1s, draw-in and endchip
glides lengthened, flow current and particles drift slower with a softer
bead pattern and wider glow, markers gain depth shadows, and the breathe
pulses relax.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four reported defects shared a root cause — the ribbons were driven by
competing mechanisms (a CSS draw-in class, a CSS transition, imperative
dash masks and appendChild markers), so state went stale and duplicated.

Now a single frame loop writes every ribbon's geometry and visible fraction
from one piece of state, and markers are keyed React state:

- a penalized or filtered connector really disappears below its stage; a
  React re-render can no longer restore a full-length dash over the cut
  (that was the lingering line);
- at most one marker per stage, so they cannot stack or contradict;
- lanes are solid — the example set now reads as example through opacity
  and its note, not a dash pattern that fought the mask;
- light mode is a first-class theme: lane colours deepen against white,
  opacities and marker tints adapt, and the canvas re-derives on theme
  change without losing an in-flight cut.

Also: a tab that never receives animation frames now renders complete
lanes instead of empty ones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…changes

An adversarial review of the new animation layer confirmed ten defects, all
rooted in state that a pure layout event was allowed to destroy.

- measure() now separates re-measuring from re-seeding: it keys on the
  diagram's identity (lanes, gap kinds, ghost/winner), so expanding a stage
  or resizing the window re-anchors geometry while cuts, markers and
  in-flight tweens continue. Previously every such event wiped them and
  replayed the whole reveal.
- The page memoizes the lane model, so the canvas stops re-measuring on
  every unrelated render (an SWR revalidation used to churn it).
- Tweens carry a channel and are applied oldest-first, so a newer intent
  always wins; a cut can no longer be stranded by a stale tween into a lane
  that stays masked forever.
- The wave skips beats while the tab is hidden instead of queuing tweens
  that all resolve in one frame on return.
- Gap geometry is read lazily, so a re-measure can't leave cuts anchored to
  a stale y; the demote marker follows the sort directly above it rather
  than assuming index 0.
- A deterministic winner's end chip stays at the converge point, its win dot
  fades while that lane is cut, restored markers get a fresh key so their
  entrance animation replays, and a theme change repaints imperative styles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Following the reference flow, each stage that can change the candidate list
now carries its own column of connector chips — who is still in the running
after that step. The gateway a stage knocks out turns red and strikes
through at that stage, then disappears from every stage below it, and the
chips slide when a re-rank reorders them.

Also fixes a stale-measure bug the columns exposed: stages appear as each
config read lands, which changes the gaps the canvas measures without
changing the lanes, so with lanes memoized the canvas never re-measured and
its labels described an older rail. The page now passes the rendered stage
list, and the canvas re-measures whenever it changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The last column now reads as a verdict rather than another list: whichever
connector holds the leading slot when the rail ends gets a green tick and a
'wins' caption in place of its lane swatch, and it updates as the re-ranks
change who is in front.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-ranks used to cross the ribbons, so the colour sitting in a given column
changed part-way down and two connectors read as swapping identity. A lane
now runs straight from entry to decision — one connector, one column, one
colour — and the re-rank states the new order on the chips instead: each
re-rank column carries the rank it just produced, and the columns below it
carry that order forward until something changes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The '✕ not eligible' / '▼ penalized' markers were positioned on their
connector's lane, which is exactly where that connector's chip now sits, so
the two overlapped in the same row. The chip already carries the identity
and the red struck-through state, so the marker only needs to supply the
reason: it now sits clear to the right of every lane, on the chip's row,
captioning the column. Their entrance animations no longer translate
horizontally, which had re-offset them once they stopped being centred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The winning chip was placed just below the converge gap, which is exactly
where the 'outcomes feed tomorrow's scores' line starts, so the two
overlapped. The arriving chips now sit inside the gap, the gap carries
enough height for them, and the caption gets a little air above it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prajjwalkumar17 and others added 7 commits September 15, 2026 13:25
… lanes

Chips were centred on their connector's lane, so the ribbon ran straight
through them — and the eliminated/restored events landed in the same place
again. Each stage now gets a single flex row placed to the right of every
lane: its connector chips in rank order, then that stage's event at the
end. Rank drives flex order, so a re-rank visibly reorders the row, and
nothing is positioned by hand any more — a lane can no longer cross a chip
and two labels can no longer claim the same spot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The names belong on the lanes — that is what ties a chip to its ribbon — so
they are back on them, and only the stage events (penalized, back, not
eligible) live in the clear space past the last lane. Lanes are spaced
wider so a full name fits on each without touching its neighbour, and the
chips (winner included) are painted opaque so the ribbon they sit on can no
longer show through them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cut landed slightly above the connector chip, so the ribbon's rounded
cap poked over the chip's top edge and read as the line crossing the text.
Cuts now land at the chip's centre, so the chip covers the terminus and the
name always sits on top of its line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stacking was already right — the chips paint above the lanes and are opaque
— but a ribbon ran flush against the chip's edge with no separation, which
reads as the line touching the text. Every label that sits on a lane (the
entry names, the per-stage connector chips, the split percentages and the
winner) now carries a ring in the card's own background, so the line is
knocked out around it and visibly stops at the label.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The entry fan swept almost horizontally straight into the connector names,
so the curves arrived at the chips at a shallow angle and read as running
across them. Each lane now curves onto its own column high in the entry
gap, then drops straight down to its name — the fan is finished well above
the row, and the names sit on a clean vertical run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rank numbers stated the new order; showing it is better. A re-rank now
moves the connector to its new position — the ribbons cross and the named
chip travels with its lane, so you watch one gateway overtake another
instead of reading '#1' and '#2'. The colour swap that made this confusing
before is gone: every stage carries the connector's name on its lane, so a
crossing reads as movement rather than two lanes trading identity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The names lagged the ribbons for two reasons. They carried a CSS transition
on the left property while the canvas was also writing that property every
frame, so the transition spent the whole animation chasing a moving target;
and a re-rank row sits at the curve's midpoint, where the ribbon is half-way
between the old and new column, while the chip was placed at the final
column — half a lane-step ahead of its own line.

Position is now frame-synced with no transition, and a chip on a re-rank row
sits on the curve. Measured drift between a name and the ribbon at its row
fell from 53px peak / 22px average to 5px / 1px. Chips below a cut now fade
exactly as the retracting ribbon passes their row instead of blinking out,
so the names fall away in step with the line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prajjwalkumar17 and others added 9 commits September 15, 2026 16:19
Putting a re-rank row on the curve's midpoint to keep it glued to its line
had an obvious flaw once two lanes actually swapped: both curves pass
through the same midpoint, so the two names landed on top of each other.

The row now sits just below the crossing, where each lane has arrived at
its new column and the ribbon is vertical again — the names stay locked to
their lines, and they are side by side in their new order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The group headings sit above the ribbon (z-[2]) but paint no background of
their own, so each lane showed through the gaps between the letters — two
lanes cut straight across 'Candidates — who can process it'.

The heading text now carries the surrounding background, the same knockout
the stage badges and connector chips already use, so a lane passes behind
the words and resumes below instead of through them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Knocking the ribbons out from behind the headings stopped them crossing the
words, but it left every lane visibly broken at each heading row. Standing
the heading beside the lanes instead keeps the ribbons running unbroken,
which reads better on a diagram whose whole point is continuous flow.

The indent follows the full lane set rather than the lanes still live, so it
holds still while the animation cuts and re-admits connectors. Below 1024px
there is no room to stand beside the lanes, so the heading stays left and the
knockout behind the words carries it there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two things were repeating rather than informing. 'Always runs' sat on four
of the rows, but a stage is drawn at all only when it runs, so the pill
restated its own presence; it now renders only for stages that actually have
something to report ('Configured', 'Elimination on', a named strategy).

The group headings restated the operation tag directly beneath them —
'ORDERING — who should get it' above an ORDERING badge. The headings keep the
half that teaches and drop the half that echoes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
They named the group but the rail already reads in order, and each one sat
directly above an operation tag that said the same thing more precisely.
Removing them takes a row of text out of every group boundary and lets the
ribbons run the length of the rail uninterrupted.

This also retires the machinery that existed only to keep them off the lanes:
the lane-band indent, its breakpoint rule, and the knockout behind the words.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The wave fired its four events at 0s, 1.1s, 3.35s and 4.3s and then sat still
for the rest of the 9.5s cycle, so the rail read as a burst followed by a
pause. Events are now one EVENT_MS apart, the wrap into the next wave
included, and the cycle length follows the number of events the merchant's
configuration actually produces.

The cost re-rank also stopped skipping its turn. It swapped only on a coin
flip, so roughly a third of cycles animated to the positions already held —
invisible at the old pace, but at five seconds it left a ten-second stall.
It now always moves an adjacent pair, chosen at random for variety.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The decision row now names what the stage actually returns — [razorpay]
[payu] — in the order the connectors would be offered, and it tracks the
rail, so a connector dropping out leaves the bracket as it happens.

This also fixes a connector with no traffic being presented as the choice. A
0% leg is truncated in path geometry rather than by the visibility mask, so
the old winner test never excluded it, and a re-rank could rotate it into the
lead. Zero-share lanes are now excluded from the output outright: their
ribbon ends at the split and never reaches the decision at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The output reads as boxes on the middle of the decision row, each with its
lane's colour, in the order the connectors would be offered — replacing the
bracketed text. 'best score + cost wins' returns to the right-hand slot,
which the output had been occupying.

Naming the arriving connectors at the decision also fills the gap the end
chips left behind: every other gap labels its lanes, so the last one running
down into nothing read as the diagram having lost its ending. They are now
ordinary chips, like every other row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail had drifted to eight sizes — 9, 9.5, 10, 10.5, 11, 12 and 13px —
four of them under the 11px floor the scale defines, which is why the small
text read as faint next to the rest of the dashboard. Everything under the
floor moves to 11px, and each size is now paired with its leading, so a chip
no longer inherits whatever line-height sits above it. The page is left on
11/12/13, the same three sizes its sibling routing pages use.

Larger chips need more room, so the gaps grow with them, and a re-rank row
sits further off the gap's bottom edge. The sort gaps take the most: their
lanes cross, and the names have to land far enough apart to stay legible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Decision Flow — visualize the routing pipeline in the dashboard

2 participants