mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-31 03:47:12 +08:00
957049e563fdc77e0a66e6aaa702eb222a78ca21
31
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b99411a86f |
fix(workflow): highlight references in code previews (#7081)
* fix(workflow): highlight references in code previews * fix(workflow): harden reference highlighting * fix(workflow): scan references in linear time |
||
|
|
b515fe0681 |
improvement(workflow): add compact code hover previews (#7074)
* improvement(workflow): add compact code hover previews * fix(workflow): address code preview review feedback |
||
|
|
6d313a4a94 |
refactor(renderer): drop the isWorkflowRunning prop the views never read (#7040)
Removing only the unused binding and keeping the prop was half a fix. The views declared it, the app passed it, and nothing read it — so the prop was dead, and dead code does not become live by being documented. Its TSDoc claimed it "holds every block's action swell open". That behavior does not exist in either view. Keeping the prop on the chance someone wants it later is the speculative-generality smell: if the toolbar should pin open during a run, that gets implemented deliberately and the prop comes back with logic behind it. Removed from both view interfaces and from both call sites. The store subscription stays — `workflow-block.tsx` and `subflow-node.tsx` each passed the same value twice, once to the dead view prop and once to `ActionBar`, which has 28 real reads and is what the surrounding TSDoc is actually describing when it says the flag "only swaps Run for Stop and disables mutations". `workflow-edge-view` uses it too and is untouched. The renderer test that passed it loses the argument. Worth noting it set the flag to stage a workflow run, and since the view ignored it those two cases were never exercising the run state they name. |
||
|
|
cae80c5f20 |
chore(lint): turn on the rules that would have caught the dead code (#7037)
Three rules were off, so nothing enforced them. Measured, fixed the sites, and enabled them where the cost is bounded. `noAccumulatingSpread` — 2 violations, both real O(n²) reducers, both now `Object.fromEntries`. One duplicates a block's subBlocks on every block duplication; the other rebuilds a Record from every workspace env var. Enabled repo-wide. `noUnusedVariables` / `noUnusedFunctionParameters` — 633 repo-wide, but only 6 under `packages/`. Fixed those 6 and enabled both at error for `packages/**` via an override, which permanently covers 979 files. `apps/sim`'s remaining 627 are left deliberately: that is a sweep of its own, and a rule enabled with 627 outstanding warnings teaches people to ignore it. This is the class of rule whose absence let the dead code in #7019 accumulate — eleven unread loggers, a whole unimported file, write-only locals — none of which any gate could see. Two of the six were in `workflow-renderer`, where the fix is narrower than it looks. `isWorkflowRunning` is destructured-but-unread in both the block and subflow views, and the app passes it from `workflow-block.tsx` and `subflow-node.tsx`. Its TSDoc claimed it "holds every block's action swell open"; nothing reads it, so that behavior does not exist. Removing the prop breaks the callers and implementing it is a UX decision — there is adjacent logic deliberately not pinning the toolbar during a handoff. So only the unused binding goes, and the TSDoc now says what is true. Not enabled: `noDocumentCookie` (3 sites, and its fix is the CookieStore API, which is a browser-support call) and `useExhaustiveDependencies` (384 errors). |
||
|
|
528b34f564 |
fix(workflow): hide idle nested subflow end handles (#6976)
* fix(workflow): hide idle nested subflow end handles * perf(workflow): avoid repeated subflow edge scans * perf(workflow): stabilize subflow edge selector --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> Co-authored-by: Waleed Latif <walif6@gmail.com> |
||
|
|
81ebb37563 |
fix(delivery): stable provider idempotency tokens, and correctness fixes around them (#6943)
* fix(delivery): stable provider idempotency tokens, and correctness fixes around them Six independent fixes found while investigating a Slack transport failure. None of them are that failure; all of them are live. **Money writes could be delivered twice.** Square (8 tools), Brex (5) and Outlook Calendar minted their provider idempotency token with `generateId()` at request-build time. That is stable inside the transport retry loop — `prepareToolRequest` runs once above it — but a BLOCK-level retry re-enters the handler and mints a fresh one, defeating the provider's dedupe. A builder ticking "retry" on a Square block turned a committed write into a second card charge, and a Brex one into a second money transfer. Tokens now come from `deriveDeliveryKey`, a pure function of execution + block + tool + invocation, so every retry layer derives the same value. Stripe joins them: all ~50 tools previously sent no `Idempotency-Key` at all. The comment on `brex/create_transfer.ts` claimed a fresh key per transfer *prevents* duplicate money movement. That is inverted — fresh per *attempt* is what permits it — and is corrected here. **`invocationId` is required, not optional.** Deriving from `executionId` alone would be worse than the bug: five loop iterations paying five invoices would share one token, the provider would honour the first and silently drop four real payments, and it would look like five successes. Also: - `INTERNAL_API_BASE_URL` is now ignored on Trigger.dev workers. It names a route that resolves only inside the app container, and several modules run in both runtimes — `guardrails/mask-client.ts` says so in its own TSDoc — so setting it produced `PII redaction failed: Unable to connect` on every worker-side redaction. Mirrored into `packages/testing`'s urls mock, which reimplements the function and would otherwise have diverged. - `engines.bun` raised to >=1.3.14. Measured on 1.2.15, which the old floor permitted: a fully-delivered POST is silently replayed and the caller sees 200. - CloudWatch `put-metric-data` pinned to `maxAttempts: 1`. The AWS SDK default of 3 already replayed it, and `PutMetricData` aggregates rather than overwrites, so a duplicate silently corrupts the customer's metric series and alarm thresholds. - `webhookIdempotency` given a bounded in-progress lease. An untimed run held a SEVEN DAY lease while concurrent duplicates polled it once a second. Verified: `tsc --noEmit` clean, 30,100 tests pass. * fix(ci): restore staging files the branch split had reverted, and format Three problems, all from assembling this branch by checking paths out of a WIP branch built on an older `staging`. Anything `staging` changed since that base came back as a revert. - Root `package.json` had lost the `opentype.js` / `@types/opentype.js` dependencies `staging` added, which desynced `bun.lock` and failed `bun audit`. It also carried a `check:outbound-delivery` script belonging to other work. Every `package.json` is now taken from `staging` with only the `engines.bun` line re-applied. - `executor/utils/block-data.test.ts` — a 77-line file `staging` added — was deleted outright. Restored. - `executor/handlers/generic/generic-handler.test.ts` had lost a test `staging` added. Restored, with only the one `blockId` assertion re-applied. Also formats `keyed-invocation-identity.test.ts` and sorts imports in `internal-api-base-url.test.ts`, which is what `lint:check` failed on. * fix(providers): thread the model's tool-call id into keyed tool execution `prepareToolExecution` accepted an `invocationId` but no provider supplied one, so a keyed tool invoked through an agent always hit the incomplete-context fallback and minted a fresh token — leaving Stripe, Square, Brex and Outlook Calendar writes able to double-deliver under the hosted-key retry layer even though the block path was fixed. The id is now a positional parameter rather than another optional field on `request`, so a provider that cannot supply one fails to compile instead of silently falling through. 22 of the 27 call sites already had the OpenAI-shaped `toolCall` in scope; `tsc` identified the other five, of which Anthropic (`toolUse.id`) and Bedrock (`toolUse.toolUseId`) name it differently. Gemini is left deliberately unthreaded and documented: its function-call parts carry no model-supplied identifier — the streaming loop has to synthesize a local one — and a positional index would not survive the model re-emitting the call. A token that only looks stable is worse than the loud fallback, which names the missing fields. * fix(executor): keep execution order monotonic across a resume `executionOrder` is not carried in the pause snapshot, so a resumed run restarted the counter at 0 and a loop or parallel body executing on both sides of a pause could reuse a pre-pause value. That was cosmetic while the number only ordered logs. It stops being cosmetic once identity is derived from it: a `keyed` tool takes its provider idempotency token from this value, so two distinct writes would present the same token and the provider would silently drop the second. Suppressing a real payment is worse than the duplicate the token exists to prevent, because it looks like success. The counter is now seeded from the highest `executionOrder` among the restored block logs rather than from a new snapshot field, so snapshots written before this change are repaired on resume instead of needing a migration. * fix(providers): thread the tool-call id through the streaming loops too The previous commit only reached call sites written as a single-line three-argument call. The streaming loops are formatted across lines, so openai-compat (Groq, DeepSeek and everything else routing through it), Anthropic and Bedrock still omitted the id and kept falling back to a fresh token. Both Gemini paths now pass `part.functionCall?.id` — the RAW model id, not the `ensureToolCallId` value used for stream events. That helper allocates an execution-local id when Gemini supplies none, and it is freshly allocated per attempt: passing it would complete the keyed context, silencing the "could not derive" warning, while leaving the token just as unstable. Gemini frequently omits the id, in which case this is `undefined` and the loud fallback stands. All 27 call sites are now covered. * fix(providers): make the tool-call id argument required, not optional The TSDoc claimed a provider that cannot supply an id would fail to compile, but the parameter was declared `toolCallId?: string` — so a new call site could omit it entirely, typecheck, and silently take the unstable-token path the positional parameter exists to close. The comment promised a guarantee the type did not enforce. It is now `string | undefined`: required in position, nullable in value. A provider with no model-supplied id must pass `undefined` explicitly and take the loud fallback, rather than being able to forget the argument. All 27 existing call sites already pass it, so this is enforcement only. Verified by deleting the argument at one site: `tsc` rejects it. |
||
|
|
85290693c4 |
refactor(search): one definition of what a search occurrence is (#6905)
Follow-up to #6901, closing two places where the workflow search index and the Note card that mirrors it could drift apart. Neither is a live bug; both are the shape that produced one — the card silently disagreeing with the panel about which hit is which, counted in one place and painted in another. THE SCAN. #6901 shared `foldSearchWhitespace` but left the scan around it duplicated: normalize, then non-overlapping `indexOf` stepping by `max(len, 1)`, written out once in the indexer and once in the renderer package. They agree today. They would stop agreeing the moment either grew whole-word matching, diacritic folding, or a regex mode, and the failure is silent. Both now call one `forEachSearchOccurrence` in `@sim/utils/string` — the only place either package can share, since the card renders from a package that cannot import from `apps/*`. THE DECLARATION. The indexer projects markdown escapes only for a field declaring `searchTextFormat: 'markdown'`; the card projects unconditionally, because it cannot read the block registry. Dropping that one line from the Note config would leave them disagreeing with nothing to catch it, so a test now pins it and explains why. Net negative in lines: this deletes a duplicated loop rather than adding a layer. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2e111f615c |
fix(search): answer a Note match on the canvas card (#6901)
* fix(search): answer a Note match on the canvas card A workflow search match inside a Note counted towards the result total and then highlighted nowhere: the editor panel renders nothing for a Note, and `clearCurrentBlock` — which the panel calls to refuse one — also cleared the shared `activeSearchTarget`, destroying the very target the card was about to paint. Searching a 15k-character note reported "1 of 6" and moved nothing. The card's read view is now the surface that answers: - A rehype plugin marks every rendered occurrence; the current one is picked by an ordinal counted with the same scan the indexer uses, and travels by context so cycling matches does not re-parse the document. - `<Streamdown>` is keyed on the query. Its memo comparator ignores `rehypePlugins`/`components`, so a plugin change alone cannot re-render it — marks appeared only when something else remounted the card, and then outlived the query that produced them. - The canvas selects, centres and expands the Note, because a compact card resets its scroll region to the top and cannot hold a position deep in its own body. Scrolling to the mark is `scrollTop` arithmetic, never `scrollIntoView`, which would drag ReactFlow's transformed viewport off-frame. - Title matches mark the name too. `activeSearchTarget` is re-published with a fresh identity on most of the search panel's renders, so subscribers take primitives. Holding the object in `WorkflowContent` — the panel's own ancestor — closed an unbounded update loop. Separately, the serializer escaped every underscore, writing `SB\_ACTION\_ROUTER\_SECRET` into the document. CommonMark's intraword rule means that backslash carries no meaning, and search matches the stored markdown, so it made anything with an underscore unfindable in a note that plainly showed it. Dropped outside code regions, where the serializer emits verbatim and a backslash is the author's own character. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(search): honour fences and folded whitespace in Note highlighting Two review findings, both real. The intraword-underscore cleanup guarded code with a pattern that recognised only the shortest delimiter forms — a bare ``` pair and a single-backtick span. A ````-fenced block, a tilde fence, or a ``multi-backtick`` span ended the region early and handed the rest of the author's code to the rewrite, turning `a\_b` into `a_b` inside their code sample. Fenced blocks are now walked a line at a time, tracking the opening delimiter exactly the way stripEmptyListItemLines already does (three or more, closed only by a run at least as long), and the inline branch matches a backtick RUN closed by one of equal length. The note scanner claimed to be the same scan as the indexer's `findTextRanges` but did not fold whitespace, which staging added since this branch was written. The indexer folds every `\s` to a space, so a phrase matches across a soft line break — which `remark-breaks` renders as a `<br>`, splitting the phrase over two text nodes that a per-node scan could never see. The hit counted in the panel and highlighted nowhere, the exact bug this branch exists to fix. The plugin now scans runs of continuously-readable text rather than single nodes, so a match spanning an inline boundary (a soft break, a bold word) is wrapped as several marks sharing one ordinal. Runs end at any non-inline element, so two paragraphs are never joined into a phrase the reader cannot see. `foldSearchWhitespace` moved to `@sim/utils/string`: the canvas card renders from a package, which cannot import from `apps/*`, and two copies of that rule silently disagreeing is precisely what produced the second finding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(markdown): close a fence only on a bare delimiter run A closing fence carries nothing but its delimiter run; a line that merely starts with one is content. The guard matched the prefix alone, so an interior line like ` ````example ` inside a same-length fence ended the block, and every cleanup below then processed the author's remaining code as prose — dropping the backslashes from their `a\_b`. Both fence walks in this file shared that flaw, so both now go through one `closesFence`, which requires the run to be followed by nothing but whitespace. Strictly more conservative: a fence stays open longer, so more content is left verbatim. Scope, stated plainly: the serializer always opens a block with one more delimiter than the longest run inside it, so its own output cannot reach this shape today, and `postProcessSerializedMarkdown` only ever sees serializer output. This is a correctness fix that removes an unstated coupling to that choice, not a live corruption path. The tests therefore exercise `postProcessSerializedMarkdown` directly — a round-trip test of the same input would pass either way, which is exactly the vacuous check worth avoiding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(markdown): skip quoted fences, and stop joining runs across inline tags Two review findings, both real, both the same shape: a rule that looked at the rendered form and forgot what the source actually says. QUOTED FENCES. The fence walk only recognised a bare delimiter run, but the serializer writes a fence inside a blockquote or a `[!NOTE]` callout with a `>` on every line. Code state was therefore never entered there and the block's interior was cleaned up as prose: `> x = a\_b` round-tripped to `> x = a_b`, losing the author's backslash. Unlike the fence-length cases this one is reachable today — verified against the real serializer before and after. Both fence walks now unquote the line first. INLINE JOINS. Runs concatenated the visible text of every inline tag, so `a<strong>b</strong>c` read as `abc` — a hit that cannot exist in the markdown the indexer scans, where `**` sits between the words. That is worse than a spurious mark: `occurrenceIndex` counts SOURCE occurrences, so a fabricated hit earlier in the document steals the current ordinal and paints the mark on text the search never matched. Only `<br>` continues a run now, because it alone stands for a character the source really has (a `\n`, folded to a space). Everything else stands for syntax the render drops. Nothing real is lost: a match spanning `a**b**c` would have to contain the asterisks to exist at all, and a match wholly inside an element is still found — the element simply starts its own run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(search): match a Note body as it renders, instead of rewriting the file Replaces the serializer change with one that writes nothing. The editor backslash-escapes every markdown-significant character in prose, so a Note the reader sees as `{{TE_SERET}}` is stored as `{{TE\_SERET}}` and search — which matches the stored value — could not find it. The previous approach undid that escape in `postProcessSerializedMarkdown`, which meant re-deriving markdown structure from the serialized string with regexes so it knew what was code. That is a losing game: three review rounds, each finding another construct it did not model (longer fences, then delimiter-prefixed lines, then quoted fences), and each miss REWROTE somebody's code. `markdown-fidelity.ts` is back to staging, byte for byte. The escape is now undone on the matching side only. A field declares `searchTextFormat: 'markdown'` (the Note body is the only one), and the indexer matches it against `projectEscapedMarkdownForSearch(value)` — a total, structure-free function that returns the rendered text plus an index back into the source. Ranges stay in source coordinates, so replace still rewrites the whole `\_` and never strands a backslash. The asymmetry is the whole point: a matcher that de-escapes something a fence would have kept literal changes only which text highlights, and no caller writes it back. A rewriter making the identical mistake corrupts the file. So there is nothing here that needs to know about fences at all. Two consequences worth having: existing notes are searchable immediately rather than after their next edit, and no stored byte changes, so no document the editor has ever written can be affected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b198080356 |
feat(credential-groups): complete managed account enrollment (#6729)
* fix(credential-groups): show reconnect after authorization * feat(slack): include managed user auth in custom bots * feat(credential-groups): add enrollment completion page * fix(credential-groups): align settings order and block color * fix(credential-groups): delete credentials on access revoke * fix(credential-groups): use person icon for enrollments * fix(credential-groups): keep enrollment submit visible * fix(credential-groups): make enrollment connections optional * fix(credential-groups): simplify people actions * fix(credential-groups): preserve pagination after deletion * fix(credential-groups): delete removed enrollments * fix(credential-groups): hydrate canvas labels |
||
|
|
b9b9c9c9a9 |
fix(canvas): stop phantom ports and a latched-open action bar (#6688)
* fix(canvas): stop phantom ports and a latched-open action bar Ports surface on hover from a swell painted on the card border, and that swell was raised with no regard for whether a handle exists behind it. A Response block mounts no source handle, so hovering its edge raised a knob no edge could ever leave from. A trigger mounts no target handle, yet still swelled under a connection dragged from another card, offering a drop it cannot accept. Gate each direction on the handle that backs it, and limit a trigger's own swell to its source edge the way the subflow start node already does. The action bar latched open for the same interaction. Leaving the card arms a retract and installs a pointermove listener to track the pointer across the gap up to the bar; re-entering the bar's band called openHover(), which cancelled the retract AND removed that listener. No further pointerleave can arrive once the pointer is off the node, so nothing was left to close the bar. Keep the listener installed and re-arm the retract when the pointer moves back out. Also clear the magnetized port when the pointer leaves the tracking band onto the action bar: only the in-band path recomputed it, so the last knob stayed pinned at hover amplitude with the pointer nowhere near it. * fix(canvas): scope the receive gate, cover both swell directions Gating the foreign-drag listener also ran the shared pointer-tracking reset, which belongs to the card's own hover. A trigger sets canReceiveConnection false while canStartConnection stays true, so the reset undid the layout effect's :hover bootstrap and left a card that mounted under the pointer with no source swell until the pointer left and came back. Skip the listener instead; the effect's own cleanup already covers a true-to-false flip. Drop the trigger-only cursorSwellSides restriction. A swell on a trigger's input edge resolves to a source handle, so an edge genuinely can be made there — it was a behavior change beyond the bug, not a phantom. Cover both directions of the swell gate, and use the shared sleep helper in the action-bar test. Hoist the constant connection sides out of the render body so they stop riding the borderPorts dep array. |
||
|
|
85c8451a86 |
fix(blocks): give the detail headers the same tile as everything else (#6672)
The tile consolidation left the large detail headers on their own treatment, so a block wore one colour on the canvas and another in the header naming it: the preview panel behind the deploy modal and the logs trace detail both painted straight from the catalog `bgColor`, showing Start's catalog blue over a neutral card, and hardcoded `#2FB3FF`/`#FEE12B` for the two subflows. Adds the 18px header slot to `BlockTile` and points all three headers at it — preview panel, trace detail, and the editor header, which had been carrying its own inline copy of the accent rule. `WorkflowTypeIcon` takes an icon class so the accent chip can draw the larger glyph the header uses. |
||
|
|
e56b28857c |
fix(workflow): draw a highlighted edge over the ordinary ones (#6642)
* fix(workflow): draw a highlighted edge over the ordinary ones An edge's z came from the nesting depth of the container it belongs to, and a highlighted edge kept that depth like any other. A line one level deeper therefore sat above it and painted straight through the highlight, cutting it in half wherever the two crossed. Give a highlighted edge — selected, or connected to the selected card — the top tier of the edge band instead. Depth only ever ordered edges against each other, and once the user has picked one out, being drawn whole matters more than which container it came from. The tier stays inside the band, below the cards, deliberately: highlighted edges were elevated over the cards once before and drew across the chrome of their own endpoints. A line belongs behind cards, knobs and the action-bar swell whether or not it is highlighted, so ordinary edges give up the top of the band rather than the band being widened into the cards. * fix(workflow): elevate the connection preview edge with the rest It renders highlighted — its data carries `isConnectedToSelection` — but it was the one call site left taking a depth tier, so the line being drawn could be crossed by an ordinary edge in a deeper container. Highlighted now means elevated with no exception. Also drop the export on the highlighted tier: nothing outside the module reads it, and the band's tiers are an implementation detail of `getEdgeZIndex`. * fix(workflow): give the edge highlight one definition The z-index elevation I added checked canvas selection only, while the edge darkens for panel focus too — a block open in the editor lights its edges, and those stayed depth-tiered, so an ordinary edge could still cut through the highlight. The bug I set out to fix, on the path I had not covered. The condition already existed in two places and the second one carries a comment saying it must mirror the first exactly, because a knob checking fewer conditions than the line leaves a dark line running into a light knob. Adding the z would have made a third copy, and the finding here is what the third copy gets you. One predicate now, in `edge-highlight`, used by the line, the knobs, and the z. The canvas subscribes to the panel store rather than reading `getState()`, since the z has to be recomputed when the open block changes. |
||
|
|
128054ea51 |
fix(workflow): stop subflows resizing themselves after every load (#6639)
* fix(workflow): stop subflows resizing themselves after every load
A container sized itself from its children, and when a child had not yet
reported a height it used `estimateBlockDimensions` in its place — a guess of
`ceil(subBlockCount / 2)` rows, which read a 39-field Gmail card as 276px tall
against the 112px it draws. The container painted that number, the real height
arrived a frame later, and it visibly resized between the two. Nothing is
persisted, so it happened on every refresh.
A card's height depends on what it actually renders — which rows survive its
conditions, whether it draws a summary sentence, and for a reactive field even
a credential it has to fetch — so the card is the only thing that can know it.
Size only from heights the children have themselves reported, and hold the
container at its current size until they have. `getBlockDimensions` keeps the
estimate for the callers that only need a rough box (clamping a drag, placing a
paste) and is now that same lookup plus the fallback.
Also stop `calculateContainerDimensions` counting the container's chrome twice.
Child coordinates are relative to the container's own origin and are already
held clear of the header by `clampPositionToContainer`, so a child's far edge is
the distance to cover and only the trailing padding is owed on top. Adding the
header and leading padding again left every container 66px taller and 16px
wider than its contents.
* fix(workflow): gate container sizing on this session's reported layout
Two holes in the measurement gate, both from reading the wrong field.
`height` is a persisted column and `data.width` / `data.height` persist a
container's last size, so a block that has not reported yet can still carry last
session's numbers — reachable through paste, import, and checkpoint restore. The
gate treated those as reported and sized from them.
Nested containers had it worse: an inner container with an unreported descendant
handed back its 500x300 default as though it were measured, so the outer
container sized to that and resized again once the descendant filled in — the
same two-step this change exists to remove.
`layout` is in-memory only and written by exactly the two places that know: a
card through `updateBlockLayoutMetrics`, a container through
`updateNodeDimensions`. Reading it means "reported during this session" and
nothing else, and an inner container that is still waiting reports null, so the
outer one waits with it.
`getBlockDimensions` keeps the persisted height and the estimate as fallbacks —
its callers only need a rough box, where a stale height still beats a guess.
* Revert "fix(workflow): gate container sizing on this session's reported layout"
This reverts commit
|
||
|
|
7022e2ef4f |
fix(workflow): stop the coloured knob leaving a barb at each shoulder tip (#6632)
The connection knob is a recolour of one stretch of the card outline, so its path has to be the outline's own path. Two things pulled it off: Its span was cut at the exact point the bulge falls under the visibility threshold, which is not one of the points the silhouette sampled — so the knob sat on a grid of its own. And a span clamped its first and last control points to the bare perimeter tangent, where the silhouette derives every control point from the samples either side of it, so those two segments bowed differently from the curve they were painted over. The knob was left still flat where the silhouette had already begun its descent, and the uncovered sliver of dark stroke read as a small spur poking off the shoulder. It is clearest on the Error output's outer shoulder, against the card's bottom-right corner. Measure the span against the interval the silhouette resamples the bulge over rather than in whole pixels, and sample a step wide on each side before trimming back, so every emitted segment has the neighbours the silhouette had. The knob's commands now come out identical to the silhouette's, which the tests pin — including for merged intervals and odd tab lengths, where measuring in whole pixels would still have landed half a step off. The painted footprint is unchanged. |
||
|
|
245ad1bd46 |
feat(workflows): new workflow block card, progress indicator, colors, dsl for natural language preview, retry configs (#6458)
* improvement(workflow): refine canvas interactions and rendering
* fix(workflow): keep outputs on the right, focus newly created blocks
Connection anchors: an output now always leaves a card from the right.
The cursor swell lets a drag start on any edge, but the left side is the
input, so anchoring an outgoing edge there drew a line out of the input
port and read as a second input. `normalizeCursorSourceHandleId` resolves
every drag to the right anchor, `normalizePositionedSourceHandleId`
collapses `source-left` alongside the legacy vertical anchors (so data
from the API, an older client, or a stale save self-heals on load), and
only the right-side source anchor is mounted.
Drops in `onConnectEnd` are always source -> target. The branch that
reversed the edge for a drag starting on an input could never run: the
`target` handle is `isConnectableStart={false}` and the positioned side
anchors are `isConnectable={false}`, so React Flow never reports an input
as a drag origin. Removed it and its now-unused imports.
A newly created block is centered once its node mounts and is measured,
so a card added from a drag-release, the block menu, or the toolbar is
never left off-screen or under the editor panel.
The editor panel's block icon uses the same type accent as the card's
badge instead of the block's legacy `bgColor`, which had left the panel
on the old per-integration brand colours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(workflow): floor header-only card height, adopt brand tag palette
The Start card intermittently collapsed after load, squashing the
action-menu tab so its icon row sat over the card.
`.workflow-drag-handle` is the host the border renderer measures, and both
it and the header row took their height from `blockHeight && blockHeight >
0`. `blockHeight` comes from the deterministic-dimensions pass and is
already floored at MIN_PAINTED_HEIGHT (48), but it is absent on the first
frames — and with no floor the host collapsed to its natural content
height (25.5px for a header-only trigger, exactly the title's line box).
The border builds its perimeter from `host.offsetHeight`, so that window
painted a sub-floor card: too little straight edge remained on the
vertical runs for the action-menu tab, which collapsed into the corner
arcs. Whether you saw it depended purely on whether the dimension publish
had landed, which is why it reproduced on one workflow and not another.
Floor all three: the host, the header row (so `items-center` centres the
title and type tag rather than pinning them to the top), and the border's
own `offsetHeight` read.
Also raise ACTION_MENU_CONTENT_READY_THRESHOLD to 0.9. At 0.8 the 24px
icon row was revealed while the swell had only reached 22.4px of its 28px
— shorter than the row it contains. Secondary to the above, but a real
overflow window on its own. The test now pins the ratio rather than the
constant.
Tag palette moves to fixed brand values (hex, not derived oklch) with two
inks — #F8F8F8 on dark fills, #1A1A1A on light. Tones are renamed to match
what they render. `green` (2.55:1) and `orange` (3.15:1) sit under WCAG AA
against their paired ink; both are deliberate brand decisions and are
documented in the component.
Deploy and Run take two new Button variants rather than className
overrides, so `tertiary` stays green everywhere else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* improvement(workflow): polish workflow canvas interactions
* fix(workflow): restyle loop drop target outline
* fix(workflow): shorten human block catalog label
* fix(workflow): canonicalize realtime edge handles
* code cleanup
* sizing fixes
* improvement(blocks): sentencify every block
* change tebse
* improvement(workflow): land notes UI and execution progress, consolidate duplicates
Ports the notes canvas editing and execution progress-indicator work, then
removes the parallel paths it arrived with so each concern has one owner.
Fixes found while consolidating:
- Note height was measured while the card was expanded to NOTE_EXPANDED_WIDTH,
where text re-wraps shorter, and published as the node's compact height. The
collapse then animated to a height never measured at the compact width.
- The edge pulse glow filter used the default objectBoundingBox units, so a
straight horizontal edge — what an auto-laid-out chain produces — resolved the
filter region to zero height and stopped the glow rendering entirely.
- A subflow's inner Start pill still read isNodeSelected while its border read
usesSelectedVisuals, so the two disagreed during execution.
- The Run/Stop button's disabled prop gated only Run while its handler cancelled
unconditionally, offering a Stop the cancel route answers with 403.
Consolidated:
- One note editor. The view's built-in textarea was unreachable in production
(the app always injects the markdown editor) and was kept alive only by tests
asserting against it; renderContentEditor is now required.
- onBlur/onCancel collapse to onEndEditing — content persists per keystroke, so
there was never a draft for a cancel path to discard.
- DEFAULT_NOTE_COLOR, the note height bounds, the note content reader and the
card width each had two or three definitions; each now has one.
- Removed with zero consumers: graphite/graphiteSubtle button variants,
data-subflow-selected, inputPlaceholderClassName, an effect that could never
fire, and getNoteColorOption's unreachable fallbacks.
Restores the role='status' announcement the progress rewrite dropped, and hardens
isNoteColor against inherited Object keys.
Co-Authored-By: Claude <noreply@anthropic.com>
* improvement(workflow): reuse the platform markdown editor in notes
Notes carried their own TipTap wiring — a second markdown editor that
reimplemented, more thinly, what `RichMarkdownField` already does for the skill
modal, skill fields and the deploy version description. It is now a ~20 line
skin: the Note supplies its type scale and per-colour selection tint, and the
field supplies the extension set, frontmatter held out-of-band, the round-trip
safety gate and its raw-source fallback, and markdown paste.
`RichMarkdownField` gains two additive props, both defaulting to today's
behaviour so the file editor is untouched: `surface` ('field' | 'bare') and
`proseClassName`. All three existing consumers pass an explicit `minHeight` and
no `surface`, so they take the original path unchanged.
Exiting the note editor moved to the card, because the editor's `/` and `@`
menus consume Escape to close themselves and ProseMirror checks `editorProps`
before plugin handlers — intercepting it inside the editor would have broken
both menus. The card now honours Escape only when nothing already consumed it,
which also let `onEndEditing` leave the injection contract.
The note editor is lazy now, matching every other consumer: it was pulling
TipTap and the full extension set into the canvas's initial chunk.
Also:
- One `areRunFromBlockDependenciesSatisfied`. The ActionBar, the canvas context
menu and the run-from-block handler each carried a byte-identical copy, and
the handler expressed the snapshot requirement differently, so the affordance
and the action could disagree. Each copy also re-scanned `edges` once per
incoming edge, on every ActionBar on the canvas.
- Reduced motion is one `usePrefersReducedMotion` in @sim/emcn rather than a
sixth ad-hoc `matchMedia`. The edge pulse now stops rendering instead of
hiding: `motion-reduce:hidden` is `display: none`, which left four SMIL
timelines running per edge.
- The pulse glow bleed covers the canvas minimum zoom. The strokes are
`non-scaling-stroke`, so the 6px tail spans 3/zoom user units — 30 at 0.1.
Co-Authored-By: Claude <noreply@anthropic.com>
* improvement(workflow): port canvas styling from workflow-updates
Ports the 14 styling commits your colleague added since the last sync, leaving
the ~68 staging PRs on that branch alone — those are platform/core work, not
this. Cherry-picked individually rather than merged so each conflict was small
enough to reason about.
What came in:
- Core block colors unify behind a two-level map: block type -> semantic role
-> accent, replacing the flat per-type table. Adds `purple` and `content`
tones to ChipTag, and a shared `WorkflowTypeIcon` that replaces the
hand-rolled ChipTag + accent lookup at each discovery surface.
- Native triggers take semantic colors; the deployments block moves to the
shared Rocket icon and drops its now-unused `iconColor`.
- Running-state polish: loader artwork and position, stop hover in dark mode,
the loader blended into the execution swell, and tooltips suppressed for
actions that are hidden mid-run.
- The toolbar drag preview clones the rendered icon container instead of
rebuilding a bgColor tile, so it matches what the canvas paints.
- The sidebar shows route-derived workspace identity instead of a skeleton
while the full record loads.
Conflict resolutions worth knowing:
- The running-loader artwork went through the shared `Loader` and back to the
custom SVG on their branch; the second commit is the intent, so that is what
landed — keeping our `role='status'` announcement layered on top.
- Two commits carried the lucide-react -> in-house icon migration along with
them. That migration is a staging change we have not taken, so our imports
stayed on lucide: adopting it in two files would leave the icon set split
across the app.
- `getMappedWorkflowTypeAccent` referenced a constant their refactor removed.
It had no consumers left once the search modal moved to `WorkflowTypeIcon`,
and their branch deletes it too, so it is gone here.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(workflow): make the subflow Start swell a real connection source
Dragging an edge into whitespace opens the add-block picker, but starting that
drag from a loop/parallel Start pill did nothing: the pill's border swell was
visual-only. Regular blocks and the container's own exit mint a draggable
cursor handle from their swell; the pill rendered only its invisible 14px
static strip, so grabbing the glowing affordance started no connection at all.
Everything downstream already worked and was nearly unreachable:
- the drop hit-test skips subflow containers, so a release inside the loop
opens the picker there (z 2000, above containers)
- handleToolbarDrop parents the new block into the container at the drop point
- it already carries the exact boundary rule for this source: a container
start handle only wires to a child of that container
The pill now runs the same cursor-handle machinery as the container view, with
one deliberate difference: its temporary handle carries the branch-cursor form
of the start id. The plain cursor id normalizes by block type — for a
container that is `loop-end-source`/`parallel-end-source`, the exit — so a
swell drag from Start would have persisted as an edge leaving the container.
The branch form passes `loop-start-source`/`parallel-start-source` through
normalization verbatim on both the picker and direct-connect paths; a test
pins that contract.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(notes): stop the field's prose classes recoloring bare-surface editing
Opening a note for editing shifted the text and turned it black: the
ProseMirror root unconditionally carried `rich-markdown-prose
rich-markdown-field-prose`, which pin the field's own ink and type ramp —
`--text-primary` at 15px/25px, then 14px/22px — overriding the card's
`text-current` at 14px/20px the moment the editor mounted.
`surface='bare'` means the host owns typography (the Note card mirrors its
rendered view via `proseClassName`), so on that surface the root now carries no
shared prose classes. The field surface is untouched. Edit mode inherits the
note colour's ink — including the caret — and sits on the same metrics as the
read view.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(workflow): give the in-flight connection line contrast inside containers
The drag line was drawn but camouflaged: its default stroke was the
resting-edge grey (#e0e0e0), which disappears against a loop body's opaque
`--surface-3` fill (~1.1:1) — so dragging an edge inside any container, nested
included, showed nothing. The z-order was never the problem; the connection
line layer already sits above every node.
The default token is now `--text-muted`, one value with contrast on every
canvas surface, still lighter than the `selected` variant so the variant
hierarchy holds. No per-surface special-casing.
Resting edges inside containers share the same camouflage (`--workflow-edge` on
`--surface-3`) — left alone deliberately: recoloring placed edges is a design
decision, not a bug fix.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(notes): match edit mode to the read view, and land the caret where clicked
Three defects, all from the read and edit views being built independently.
1. Blocks jumped up ~12px on entering edit mode. Streamdown wraps its output in
a container carrying `space-y-4` plus first/last margin resets, which outrank
the per-element margins in NOTE_COMPONENTS — so that wrapper, not those
margins, is what the read view actually paints. The editor had no equivalent.
The rhythm is now named (NOTE_MARKDOWN_FLOW), passed to Streamdown
explicitly so a dependency upgrade cannot move the read view out from under
the editor, and mirrored on the ProseMirror root. Tailwind's JIT only sees
literal strings so the mirror cannot be composed from the constant; a test
pins the two together instead, and fails if either side drifts.
2. The caret was barely visible: it inherited the note's 75%-opacity ink. The
palette owns per-colour chrome, so it now names the caret alongside the
selection tint.
3. The caret always landed at the document end. The read view sits under a
full-bleed overlay that must swallow the click to enter editing, so the
point never reached the editor and `autofocus: 'end'` was all that was left.
The view now forwards that point and the field resolves it through
`posAtCoords` on create — after the DOM is laid out, which `autofocus`
cannot wait for. Keyboard activation carries no point and still lands at the
end.
`autoFocusAt` is additive on the shared field and defaults to null, so the file
editor and the other three consumers are unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(workflow): stop edges rendering behind top-level subflows
Containers are z-indexed by nesting depth, so a top-level subflow is 0. Edges
derived their z from their parent container — `+1`, or 0 with no parent — so a
root-level edge landed on exactly the same z as a root-level subflow. Equal
z-index falls back to DOM order, and React Flow paints the nodes layer after
the edges layer, so the container's opaque body won: any edge crossing a
top-level loop or parallel was drawn behind it, in-flight or persisted.
Edges now sit in their own band above the whole container scale and below
cards, keeping both the deeper-container-wins ordering and the rule that a line
always passes behind card chrome. This is why the edge became visible only once
a block was dropped: the new block is selected, and an edge inside the
container was already `containerZ + 1`, clear of the tie.
The in-flight connection line is declared in the same scale rather than
inheriting React Flow's stylesheet default of 1001, which is both below a
selected container child and outside the scale this file owns. Its stroke moves
to `--text-secondary`, the token the canvas already uses for an active edge —
the previous `--workflow-edge` grey is ~1.1:1 against a subflow body.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(notes): paste and drop images through the workspace-file pipeline
Traced the file editor's image path end to end and reused it verbatim rather
than minting a note-specific source: insertImages -> useUploadWorkspaceFile ->
POST /api/workspaces/{id}/files/presigned -> direct-to-S3 PUT -> workspace_files
row -> the editor persists the workspace-scoped
/api/workspaces/{id}/files/inline URL, which the serve route authorizes by
workspace membership and the embedded-image-ref machinery already recognizes
for share rewriting and referenced-by-doc tracking.
The shared field gains an optional `uploadImage(file) -> {url, alt} | null`
capability. With it, image paste/drop uploads sequentially and inserts each at
the evolving position, mirroring the file editor's flow, with a bail if the
editor unmounts mid-upload; without it, the existing swallow-guard on file
drops is unchanged, so the skill modal, skill fields and version-description
consumers behave exactly as before. The upload mutation owns its own toasts.
The note host wires the capability with folderId null, so note images land in
the workspace Files root — visible, manageable and deletable there like any
other upload. The note read view renders images through its Streamdown
components map with the card's own sizing.
Co-Authored-By: Claude <noreply@anthropic.com>
* improvement for notes, subflows
* fix(uploads): surface the server's message when a multipart upload is refused
A file over the 50MB direct-PUT threshold goes through multipart initiate, which
is where the storage quota is enforced — but the client threw away the response
body and reported `Failed to initiate multipart upload: Payload Too Large`. That
is the string the upload mutation puts in its toast, and it names neither which
limit was hit nor by how much, so the one place that answer surfaces didn't have
it.
It now prefers `errorBody.error` exactly as `getPresignedUploadInfo` already does
on the single-PUT path, and passes the body through as the error's details.
Control flow is unchanged: still throws, still `MULTIPART_ERROR`, and the
cloud-storage-absent branch above still claims its 400 first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(notes): restore GFM in the note read view
Streamdown's `remarkPlugins` prop REPLACES its default plugin list rather than
extending it, and remark-gfm is one of those defaults. The note passed
`[remarkBreaks]` — so the read view silently lost every GFM construct: task
lists, tables, strikethrough and autolinks.
The editor writes all of them (it has TaskList, TableKit and Strike), so a note
round-tripped through editing came back as raw source the moment editing closed:
`- [x] HELLO` rendered as a disc bullet followed by the literal text `[x] HELLO`.
`NOTE_COMPONENTS` has carried table/thead/tbody/tr/th/td entries this whole time
that could never fire.
Restoring the plugin is only half of it: remark-gfm marks a checklist
`contains-task-list` and emits a native checkbox, which under the note's generic
`ul` styling renders a checkbox sitting behind a disc bullet — the same defect
the editor had before the chrome/typography split. The read view now drops the
marker and indent for a task list, lays the row out as a flex line, and styles
the checkbox to match `.rich-markdown-nodes input[type="checkbox"]` declaration
for declaration, tick clip-path included, so the two views agree either side of a
click.
`remark-gfm` is now a declared dependency of the renderer package rather than one
borrowed transitively from streamdown.
Five tests cover the GFM surface — checked/unchecked boxes, no literal `[x]`, the
marker only dropped for checklists, tables, strikethrough — and four go red with
the plugin removed.
Checked the other three Streamdown call sites (Chat, the chat interface renderer,
the changelog): none override `remarkPlugins`, so none were affected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(canvas): drop dead markers and share the tile-brightness maths
Review pass over the branch against staging.
Dead code removed:
- `tileIconColorClass` in the renderer package — never called; only its
`isLightTileColor` sibling is.
- `data-connection-selector-search-frost`, `data-workflow-cursor-edge` and
`data-workflow-cursor-source-side` — written on three elements, read by no
stylesheet, selector or test.
- `CHIP_TARGET_SELECTOR_TYPES`, `MAX_CHIPS` and `chipPriority` were exported from
`canvas-rows.ts` but only used inside it.
Consolidated the one real divergence: the renderer package carried a hand-copied
mirror of the app's perceived-brightness maths, because it may not import app
code. The copy had already drifted — it dropped the `white`/`black` keyword
handling, so a block shipping `bgColor: 'white'` would render a white
`currentColor` icon on a white tile on the canvas while every other surface drew
it black. No block ships one today, which is exactly why nothing caught it. The
function now lives in `@sim/utils/color` and both sides import it; only the
0.75 threshold stays local to each.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(canvas): share the z-scale, fix the preview's edge layering, and re-home strays
The preview canvas carried its own z numbers and had the collision the editor
canvas was fixed for: containers at nesting depth, top-level cards at an implicit
0, and edges at 0/5/10 by execution status — so a default edge tied with a
top-level subflow and painted behind it, while a success edge painted over
unselected cards.
The scale now lives once, in `@sim/workflow-renderer/canvas-layers`, and both
canvases read it. The preview keeps its status ordering, expressed inside the
shared edge band rather than as a second set of magic numbers.
Placement and duplication:
- `perceivedBrightness` moved to `@sim/utils/color`, with its unit test, and its
consumers import it directly. It had been re-exported through
`lib/colors/brightness.ts`, and the renderer package kept a hand-copy.
- `filterAcyclicEdges`/`wouldCreateCycle` were pass-through wrappers in the
workflow store's utils over the real implementations in `@sim/workflow-types`.
Deleted; the three consumers import the source.
- `lib/ui/glass-surface.ts` was a one-constant, one-consumer app-wide module, and
its consumer then aliased it a second time. Collapsed into the navbar shell.
- `nested-subflow-node` was set on nested container nodes in both canvases with no
stylesheet, selector or test behind it.
`packages/workflow-renderer` now has its own vitest config, so the four mount
tests for its components live with the components instead of in
`apps/sim/lib/workflows/**`. That immediately earned its keep: `apps/sim`
excludes test files from type-check, and once these were checked, tsc found three
`SubflowNodeView` renders being handed a `renderContentEditor` prop it does not
accept — a copy-paste from the note cases that had been silently ignored.
Verified: type-check 23/23, 21,143 app tests + 49 renderer + 147 utils, biome
clean, all 23 audits pass (`check:bare-icons` imported the moved helper and was
repointed).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix field noun bug + notes
* fix(notes): let a note service the canvas actions the panel editor cannot
The panel editor clears any note put in front of it and renders nothing, but the
block menu still routed Rename and Open Editor through it.
Rename latched the editor's rename state onto the note — `handleStartRename`
reads the store directly, so it saw the id the menu had just set — and nothing
reset it when the clear ran. `handleSaveRename` writes to `renamingBlockIdRef`,
so the header went on showing a rename field over whatever was selected next and
saved that name to the note. Open Editor was a plain no-op that opened an empty
pane.
Rename now goes to the card, which expands and opens its own title — the same
menu-to-card routing Add Image already used, so both events now live in one
`lib/workflows/notes/canvas-requests.ts` and `add-image.ts` keeps only its
markdown concern. Open Editor is hidden for notes.
The panel editor also drops any rename whose block stops being the selected one.
That is belt-and-braces for notes now, but it closes the same hole for ordinary
blocks, where only the input's blur ended a rename and blur only fires if it held
focus. A rename interrupted that way is now discarded rather than left pending.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(canvas): author sentences for Snowflake and Mintlify, repoint Instagram's
Staging's two new integrations shipped without a `canvasPresentation`, so their
39 operations painted the field rows the rest of the canvas has stopped using.
The Instagram break is the more interesting one: staging renamed the insight
metrics subblock `metrics` -> `insightMetrics` while this branch was adding
sentences that named `metrics`. Both hunks merged cleanly — the union check
reports the file as an exact union — and the result was two clauses pointing at
a field that no longer exists, which resolves to nothing with no error and no
log. Only `check:canvas-sentences` sees it.
Two Snowflake sentences say something the block does not do, so they anchor
elsewhere: `taskName` filters `list_task_runs`/`get_task_run` rather than keying
them, and `table` filters `introspect_schema` — blank means "every one", not "not
filled in yet", and a core chip would have claimed otherwise.
Coverage is back to 4727/4727 operations across 321/321 blocks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(canvas): keep the block-type tag naming its type after a rename
The header tag dropped its label whenever the block's title already said the
same word, so the same block read two ways depending on nothing the user did
deliberately: a freshly dropped Wait showed a bare icon, and its second copy —
auto-named "Wait 2" — showed "Wait". The tag looked like a badge that appeared
on rename rather than a fixed part of the header.
It now always names the type, which is what loop and parallel containers already
do with their own tag, so every card on the canvas reads the same way.
`blockName` was only ever read for that comparison, so the prop is gone rather
than left behind for a future reader to wonder about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(deploy): compare edge handles by port, not by spelling
Two places answer "does this need redeploying?" and they load their sides
differently. The client diffs the live store against `/api/workflows/[id]/deployed`;
the server diffs the normalized tables against the version's raw jsonb. Only
some of those paths run handles through `loadWorkflowFromNormalizedTables`, so a
snapshot holding a side-anchored id (`source-right`) met a canonical one
(`source`) on the other side and the set comparison read it as every edge being
removed and re-added.
Each answer therefore differed, and they arrive on separate query timelines: the
button reads the client's, the modal badge reads the server's, so the state
flipped between Live and "Update deployment" with whichever query landed last
until both settled.
`normalizeEdge` now canonicalizes both handles, so the comparison cannot tell
two spellings of one port apart no matter how its inputs were loaded. The
existing normalization in `materializeDeploymentState` stays — that path also
feeds React Flow, which needs the handle it mounts to match.
The preview's error port had the mirror problem: it rendered for every
non-trigger block regardless of `errorEnabled`, so a card with no error row grew
a red knob anyway. It now gates the way the editor canvas does, keeping the port
mounted when an error edge already leaves it so React Flow cannot drop that edge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(deploy): stop counting the error flag twice in change detection
`errorEnabled` has two homes. It persists inside the block's `data` jsonb — the
realtime server `jsonb_set`s it there, and load mirrors it back onto the block as
a field — so it reached the diff twice, and only some paths populate the copy.
`setBlockErrorEnabled` writes the mirror alone, so right after toggling the port
the live block said `errorEnabled: true` with `data.errorEnabled: false`, while
the snapshot the deploy had just taken from the tables said true in both. The
diff read the stale `data` and reported the workflow as changed the instant it
finished deploying — then a state refetch rehydrated the block and it agreed
again. That is the flip between Live and "Update deployment": the button and the
modal read two different queries, so each landing swapped the answer. A block
created in-session had the same shape from the other side, its `data` carrying no
key at all against a persisted `false`.
Excluded from `normalizeBlockData` alongside the other fields that are duplicated
out of the block's own state. The block field is still compared on its own, with
`!!`, so absent and `false` agree and turning the flag on is still a change.
Fixing the store to write both homes was the other option and is not taken:
nothing reads the in-memory `data.errorEnabled` (save and load both let the block
field win), so it would add a second copy that only the diff could see — which is
the shape of this bug, not its fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(blocks): give the error-output flag a column instead of two homes
`errorEnabled` had no column, so it persisted inside the block's `data` jsonb
and was mirrored onto the block as a field on load. Every writer had to route
its `data` through `withPersistedErrorEnabled` or silently drop the toggle, the
realtime op `jsonb_set`, and change detection saw the same value twice — which
is what made the deploy badge flip between Live and "Update deployment" after
toggling the port.
Its siblings — `enabled`, `horizontal_handles`, `advanced_mode`, `trigger_mode`,
`locked` — are all boolean columns; `data` is for React Flow and subflow state.
The flag belongs with them, so it now has `error_enabled` and one home. The
shuttle helper, its `BlockData` mirror, the store's fallback read, and the
comparison exclusion the duplication forced are all gone.
Backwards compatibility, since released versions draw the error port with no
toggle in front of it: a block already wired to an error edge HAS the output on,
because there was no other way to draw that edge. That rule is now stated in
three places and none may be narrowed to read the flag alone —
- the migration backfills `error_enabled` from the edges, so live rows are true
before any new code reads them;
- `materializeDeploymentState` derives it for a version's frozen jsonb, which the
migration cannot reach — otherwise every workflow deployed before the toggle
would ask to be redeployed once;
- `workflow-block.tsx` keeps it at render time for states that reach the canvas
through neither (imports, copilot edits), where unmounting the port would make
React Flow drop the edge leaving it.
The migration also moves any `data.errorEnabled` a developer created on this
branch onto the column and strips the key; both statements match zero rows in
production, where it never shipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(canvas): realign the Snowflake and Dynatrace sentences with staging's blocks
Both breaks are the class the union check cannot see: separate hunks of the same
file merged cleanly, and the result names fields that no longer exist. A sentence
that does resolves to nothing, with no throw and no log.
Snowflake's rewrite (#6474) moved database, schema, table, warehouse and
procedure onto canonical selector pairs, so seven clauses anchored on ids that
are gone. Each now names both members of its pair, which is also what keeps the
card readable for someone working in advanced mode. Its nine new operations have
sentences.
Dynatrace (#6463) scoped the mute reason to the operations that mute, because
unmuting accepts exactly one — so the two unmute sentences were asking for a
field their card no longer shows. They drop the clause.
Coverage is 4736/4736 operations across 321/321 blocks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(api): drop the block-data error flag from the workflow contract
Left behind by the consolidation: the flag no longer lives in `data`, and a
schema that still declares it there invites the mirror back through the wire.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(deploy): compare edge handles by port, so a falsy one cannot read as changed
`loadWorkflowFromNormalizedTables` now runs handles through the canonicalizer,
which falsy-coalesces — so an edge persisted with `sourceHandle: ''` loads as no
handle at all. The server diffs that against the deployment version's raw jsonb,
which still has `''`, and the set comparison reads one edge as removed and
another added. Every workflow holding such an edge would ask to be redeployed
the moment this ships, for nothing. Two write paths use `?? null` rather than
`|| null`, so `''` is reachable.
Canonicalized inside `normalizeEdge` rather than at either call site: the two
sides are loaded by different paths and only some of them normalize, so the
comparison has to be unable to tell two spellings of one port apart however its
inputs arrived.
This is the change reverted in
|
||
|
|
08af7db910 |
improvement(ui): unbold the app, align the folder chevron, tidy the feedback modal (#6400)
#6241 deleted the --font-weight-* scale from globals.css and its fontWeight mapping from tailwind.config.ts. font-medium jumped 440/480 -> 500, font-semibold 500/550 -> 600, and body dropped 420 -> 400, so every existing call site snapped a full step above a body that got lighter. #6291 fixed packages/emcn only; the product call sites were left behind. Strips the weight class from body, label, row, and heading text across app/workspace, ee, workflow-renderer, the non-workspace route groups, and components/ui/button.tsx, whose buttonVariants injected font-medium into every consumer. Keeps it only where it steps up: markdown/prose bold and micro avatar initials. Also aligns the workflow-tree folder chevron to the sidebar section header (14px, 150ms), and on the feedback modal drops the prompt line above the Feedback field and re-homes Copy ID as a footer secondary action. |
||
|
|
9b9da81a27 |
improvement(platform): drop lucide-react for the in-house icon set, flatten the type and border scales, and retire scheduled tasks and workflow references (#6241)
* border styling * improvement(platform): migrate off lucide-react, flatten the font-weight scale, and retire scheduled tasks and workflow references * chore(platform): drop the dead schedule client layer and repair stale rule and skill docs Follow-up cleanup for the platform commit, which removed the workspace scheduled-tasks surface and migrated off lucide-react. Both left dead tails that type-check clean, so nothing flagged them. Six mutation hooks in hooks/queries/schedules.ts lost their only consumer when the scheduled-tasks page was deleted: useDisableSchedule, useResumeSchedule, useDeleteSchedule, useExcludeOccurrence, useUpdateSchedule, useCreateSchedule. They are removed along with the three contract objects that served only them — disableScheduleContract, excludeOccurrenceContract, deleteScheduleContract. disableScheduleBodySchema and excludeOccurrenceBodySchema are deliberately kept: both are members of scheduleUpdateSchema, the discriminated union the live PUT /api/schedules/[id] route parses. Dropping them would collapse the union and 400 the disable and exclude_occurrence actions. The schedule-calendar tree and its utils stay unmounted for later reuse. Its TSDoc now says so, since it has no importer and would otherwise read as dead code on the next sweep. The add-enrichment skill templated an import from lucide-react, a dependency the platform commit deleted, so running it produced an unresolvable import. It now points at @sim/emcn/icons, matching all five shipped enrichments. The emcn-design-review skill and several rule files still pointed at apps/sim/components/emcn/**, which moved to packages/emcn/**. Also corrects the documented Chip variant list — it advertised a ghost variant that never existed and omitted border — repoints the sim-url-state date-parser example at an inline snippet now that its source file is gone, and normalizes the one strokeWidth the icon migration left at 1.5 in bubble-chat-delay. * fix(platform): mark the resource chrome as client components `skills/page.tsx` is a Server Component, and this branch moved its `IntegrationTabsHeader` import onto the `@/app/workspace/[workspaceId]/components` barrel. That barrel re-exports `SortDropdown` from `resource-options`, which calls `useState`, so the server graph now reaches a client-only module and `next build` fails. `resource-header` has the same latent problem (`useState`, `useEffect`, `useRef`). Both files are genuinely client components, so they get the directive rather than the page dropping the barrel import — local feature barrels are the convention here. Also drops a stale `lucide-react` mention now that the dependency is gone. * chore(scheduled-tasks): remove the scheduled-task logic Scheduled tasks are retired. This removes the `sourceType = 'job'` half of `workflow_schedule` from the application, leaving the workflow Schedule trigger (`sourceType = 'workflow'`) untouched. Gone: - the job orchestration layer (`lib/workflows/schedules/orchestration.ts`) and the agent-job runner in `background/schedule-execution.ts` - the job claim/dispatch half of the schedules execute tick - POST /api/schedules (job creation) and the job branches of GET /api/schedules and PUT/DELETE /api/schedules/[id] - the copilot job tools and handlers, the `scheduledtask` resource type and chat-context kind, and the VFS `jobs/` materialization - the scheduled-task analytics events and the job variant of the schedule-disabled email Kept on purpose: `scheduled-tasks/components/schedule-calendar/**` and `scheduled-tasks/utils/**`, which the agents module will reuse. `packages/db/schema.ts` is deliberately untouched — the columns stay for now and come out in a follow-up with a proper expand/contract migration. The generated copilot catalog and VFS snapshot types are regenerated from the matching copilot PR, which removes the tools and the `jobs` snapshot field at the source. Verified: 23/23 type-check, biome, api-validation, production build, and the full vitest suite (18361 passing; the one failure in executor/handlers/pi/cloud-review-tools.test.ts predates this branch). * fix(sidebar): derive the settings and switcher widths from SIDEBAR_WIDTH This branch moved `SIDEBAR_WIDTH.DEFAULT` from 248 to 238 but left two hardcoded `248px` chrome widths behind, so both sat 10px wider than the live sidebar: - the workspace-switcher menu, which is meant to line up with the sidebar column it drops out of - the standalone settings sidebar, whose own comment says to keep it in step with the in-workspace chrome Both now read `SIDEBAR_WIDTH.DEFAULT` directly rather than repeating the number, so the next change to the constant cannot leave them stale again. * fix(schedules): stop the API accepting actions it no longer handles Adversarial pass on the scheduled-task removal found a real regression in PUT /api/schedules/[id]. Removing the job-only `update` and `exclude_occurrence` handlers left them in `scheduleUpdateSchema`, so those bodies still parsed. The handler chain is `disable` first and then an unguarded fall-through to reactivate, so an `action: 'update'` request would have silently REACTIVATED the schedule instead of being rejected. Both actions are dropped from the discriminated union, so `parseRequest` now rejects them with a 400. Their bodies, response types and the orphaned `createScheduleContract` (its POST route is gone, and nothing imported it) go with them. * chore(landing): retire the scheduled-tasks marketing surface The feature is gone from the product, so the marketing pages stop selling it. - deletes the `/scheduled-tasks` landing page and its calendar-loop hero, and the `LandingPreviewScheduledTasks` panel - drops the view from the landing preview: the `SidebarView` member, the nav entry and its now-unused Calendar icon, the callout label, both render branches, and the staged chat copy in `workflow-data` - removes the navbar and footer links and the sitemap entry - removes the route from `LANDING_ROUTES`, the COEP exemption list that must list every `app/(landing)` route `/scheduled-tasks` is indexed, so it 301s to `/workflows` rather than starting to 404 — that is the surface that still carries scheduled execution via the workflow Schedule trigger. Left alone deliberately: `demo-scheduler` is the Cal.com booking embed for the demo page, unrelated to this feature, and the scheduling library article is a generic SEO piece that never pitched it. * perf(chat): stop the resource picker fetching schedules it no longer shows Dropping the `scheduledtask` group from the add-resource dropdown left `useWorkspaceSchedules` behind, so the picker still issued a workspace schedules request whose result never reached a group. Worse than a wasted request: `schedulesPending` was still in the hydration gate, so the whole picker waited on that response before it could settle, and `schedules` was still a `useMemo` dependency, re-running the group build when it resolved. The hook and its route stay — `/api/schedules?workspaceId=` still correctly lists workflow schedules, unlike `createScheduleContract`, whose route this branch removed. * chore(scheduled-tasks): drop the leftovers the removal stranded An independent audit of the branch turned up dead code and stale docs that the compiler cannot see — nothing behavioural, but all of it rots silently. - README still sold the feature: the "Scheduled tasks" tile, the prose listing it as a workspace surface, and the now-unreferenced screenshot. The landing surface went in c61770a8c; this tile was missed. - `resource-content.tsx`: `SCHEDULE_STATUS_LABEL`, `formatScheduleInstant` and `ScheduledTaskField` were orphaned when the schedule render branch went. - `computeNextRunAt`: zero callers, including tests — its only consumer was the removed agent-job runner. - `applyScheduleUpdate`'s `allowCompleted` option: no call site passes it, and its comment described self-completion, which no longer exists. The guard stays (legacy `sourceType='job'` rows still carry `status='completed'` until the DB follow-up); it is simply unconditional now. - Three TSDoc blocks still described a create-job route and "opening a scheduled-task artifact". Type-check re-run with --force, since a cached turbo replay is not a check. --------- Co-authored-by: Waleed Latif <walif6@gmail.com> |
||
|
|
5c29d833af |
improvement(ui): drop full-content hover tooltips (#6128)
* improvement(ui): drop full-content hover tooltips - remove the chunk-content tooltip in the KB chunks table (it forced the full chunk body on every truncated row) - remove the sub-block value tooltip on collapsed workflow blocks (whole prompts/code/JSON on hover) - replace the native `title` on subflow and note blocks with the clip-gated OverflowSpan - clip-gate the KB documents tags cell so it stops firing on fully visible tags - drop the dead tooltip on the resource header root title, which can never truncate * fix(workflow-renderer): default the optional note name for OverflowSpan |
||
|
|
a042f0bd0f |
chore(deps): fix OTel version split, drop dead deps, declare emcn peers (#5994)
* chore(deps): fix OTel version split, drop dead deps, declare emcn peers
- Pin @opentelemetry/{resources,sdk-metrics,sdk-trace-base,sdk-trace-node}
to exact 2.7.1 so they match sdk-node's pins instead of floating to 2.8.0.
The carets meant app code built spans with 2.8.0 and passed them into
NodeSDK from 2.7.1, which only worked by duck-typing.
- Declare the 13 packages @sim/emcn imports but never declared, as peers
mirrored into devDeps. @radix-ui/react-dismissable-layer had no
declaration anywhere in the repo and resolved only transitively.
- Remove ffmpeg-static: its binary downloads via postinstall, but it is not
in trustedDependencies and Docker installs with --ignore-scripts, so the
accessSync branch never succeeded and both call sites always fell through
to system ffmpeg.
- Remove critters + experimental.optimizeCss: Next only loads critters from
the Pages Router renderer, and apps/sim is App Router only.
- Make simstudio-ts-sdk zero-dependency by dropping node-fetch for native
fetch; engines >=18.
- Remove unused @vercel/og and postgres from docs, dotenv/inquirer/listr2
from the CLI, and yaml from the root.
- Move @aws-sdk/client-appconfig from the root to apps/sim, its only consumer.
- Delete the apps/sim overrides block; Bun only honors top-level overrides.
- Bump free-email-domains 1.2.25 -> 1.9.70 (4,779 -> 13,059 domains).
- Validate object/array variables with JSON.parse instead of JSON5, matching
what the executor actually parses.
- Swap the changelog GitHub icon off lucide to GithubOutlineIcon, matching
the navbar chip on the same page.
- Unify @types/node on 24.2.1 and lucide-react on ^0.511.0; bump chalk to 5
and image-size to 2.
* fix(deps): complete the OTel pin, restore SDK error detail, revert email list
Follow-ups from an independent audit of the previous commit.
- Pin @opentelemetry/sdk-node and the three otlp-http exporters to exact
0.217.0. Pinning only their four dependents was self-reversing: sdk-node
0.219.0 requires core 2.8.0 exactly, so the next update would have
silently rebuilt the split this PR removes.
- Declare @opentelemetry/core (2.7.1). It is imported by
lib/copilot/request/go/propagation.ts but resolved only by hoisting, and
it is the OTel package with the most version churn in the tree.
- Pin @radix-ui/react-dismissable-layer to exact 1.1.13 in @sim/emcn. All
five transitive parents pin it exactly; a caret would fork a second copy
on 1.1.14, which is the duplicate-context bug the declaration prevents.
- Surface error.cause in simstudio-ts-sdk. Native fetch reports network
failures as a bare "fetch failed" and puts the reason on cause, so every
DNS/TLS/refused error was reaching callers with no diagnostic content.
- Revert free-email-domains to 1.2.25. Upstream now merges the free-domain
list with two disposable-email blocklists, so 1.9.70 classifies real
organization domains as free — UK charities, some companies and
universities, and the JP/KR ISP domains APAC SMBs use for business mail.
The demo form blocks submission on that check, so a false positive costs
the booking entirely. Worth doing deliberately, not inside a deps change.
- Lower packages/cli engines to >=18. chalk 5 and commander 11 both accept
>=16 and the source uses no Node 20 API, so >=20 only produced EBADENGINE
for Node 18 users.
---------
Co-authored-by: Waleed Latif <waleed@simstudio.ai>
|
||
|
|
0199508706 | fix(custom-blocks): stop image icons blowing up on class-sized surfaces (#5878) | ||
|
|
a360cd4d7c | fix(workflow): align condition/router handles with their rows (#5831) | ||
|
|
2f31981fbf |
improvement(blocks): two-tier sunset schema (legacy amber / deprecated red) (#5793)
* improvement(blocks): two-tier sunset schema (legacy amber / deprecated red) * fix(blocks): require replacedBy on legacy sunset blocks in registry check * improvement(blocks): reword legacy badge tooltip |
||
|
|
53c2716391 |
feat(blocks): surface deprecated block and model warnings on canvas (#5785)
* feat(blocks): surface deprecated block and model warnings on canvas * improvement(blocks): simplify deprecation derivation, drop unused getModelReplacement * fix(blocks): make deprecation badge keyboard-accessible |
||
|
|
9ee499e9f7 |
fix(canvas): raw tooltip shows up when hovering block params (#5589)
* fix(canvas): replace native title tooltip with styled overflow tooltip on block params Hovering a truncated subblock value or block name on the canvas popped the browser's raw native tooltip with the full untruncated content (including raw code). Replace the `title` attribute with the cursor-following styled Tooltip, shown only when the text is actually clipped. * fix(canvas): drop unused ResizeObserver in OverflowSpan useFloatingTooltip's canShow already receives the hovered element, so measuring overflow via useIsOverflowing was redundant — every canvas row was paying for a ResizeObserver and resize listener it never used. |
||
|
|
d078c84ee6 |
chore(typescript): upgrade to TypeScript 7 (native Go compiler) (#5521)
* chore(typescript): upgrade to TypeScript 7 (native Go compiler) Bumps typescript to ^7.0.2 across every workspace package. Full bun run type-check/lint/build/test all pass; apps/sim's type-check (the one needing an 8GB heap bump) drops from ~55s to ~7s wall time. Migration fixes required by TS7's stricter defaults: - baseUrl removed: drop it from 5 tsconfigs (paths already resolved relative to tsconfig dir, so behavior is unchanged) and prefix the one bare (non-relative) paths entry each in apps/sim and apps/realtime with './' - moduleResolution=node10 removed: switch packages/cli and packages/ts-sdk to "bundler", matching the rest of the monorepo - types now defaults to [] instead of auto-including every @types/* package: add "types": ["node"] to the shared base tsconfig (this is fundamentally a Node monorepo, so this restores prior behavior in one place instead of duplicating it per-package), add explicit @types/node deps to packages that now rely on it transitively via @sim/db/@sim/logger, and add "declare module '*.css'" to the two packages with plain (non-module) CSS side-effect imports that TS7's stricter checker now flags - packages/logger's isomorphic `typeof window` check no longer needs DOM lib in every consumer: replaced with `'window' in globalThis` - packages/testing and apps/realtime's fetch/DOM mocks need DOM lib where they're compiled, since they model the browser Fetch API - the `typescript` npm package no longer exports the classic Compiler API from its main entry (moved to unstable/ast subpaths); apps/sim's Function-block route used it at runtime to strip import statements from user code, so that one call site now uses Microsoft's official transition package, @typescript/typescript6 - Next.js 16.2.6's own TypeScript-detection heuristic hardcodes a path TS7 no longer ships, and its auto-install fallback assumes npm/pnpm; added @typescript/native-preview as a devDependency to apps/sim and apps/docs so Next detects a valid native compiler instead of trying (and failing) to auto-install one Not merging yet: TS 7.0.2 published today and is still inside this repo's bunfig.toml minimumReleaseAge (7-day) supply-chain gate, so `bun install` will fail for everyone until 2026-07-15. Opening this now to get it through review; hold the actual merge until then. * fix(typescript): address Greptile review findings on TS7 upgrade - packages/logger: 'window' in globalThis treats a shim that leaves globalThis.window explicitly undefined as browser-only, silently dropping production server logs. Restore the original typeof !== 'undefined' semantics via an inline cast instead, so it stays correct without requiring DOM lib in every consumer. - packages/ts-sdk, packages/cli: both are tsc-built, published as Node ESM (package.json "type": "module" with an "exports" map). "moduleResolution": "bundler" is too permissive for that target - it accepts import patterns (e.g. extensionless relative imports) that Node's actual ESM resolver rejects at runtime. Switch both to "module"/"moduleResolution": "nodenext", the correct pairing for a published Node ESM package. Verified real tsc builds (not just --noEmit) still succeed for both. * chore(bunfig): temporarily disable minimumReleaseAge gate for TS7 install TS 7.0.2 published today, still inside the 7-day gate. Lowering to 0 to unblock this merge; will restore to 604800 in an immediate follow-up commit right after merging. |
||
|
|
ad19f7fc40 |
improvement(landing): refine hero and mothership visuals (#5181)
* stash * feat(landing): mothership feature stages + pre-footer CTA Tell-then-show landing: the Mothership section defines the five capabilities (Mothership · Pod · Formation · Dispatch · Return); the Features section now shows each as a real Sim UI callout floating over a static, edge-faded platform backdrop (Linear's "callout over a faded platform" pattern). - FeatureStage template: copy + masked static LandingPreview + elevated callout - LandingPreview: static autoplay=false snapshots with per-stage view/workflowId - Callouts: Mothership chat, model picker, parallel-agents Formation graph, deploy targets, logs table - Pre-footer CTA set over the Mothership render; removed the old capabilities grid Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): reusable platform-page + solutions-page layouts and routes Add config-driven, padding-safe layouts consumed by route pages: - platform-page: hero (shared CTA) + centered logos + N card rows (3|4), JSON-LD, single <h1>, server-only; Workflows route as first consumer. - solutions-page: structural mirror (kept separate to diverge later); IT, Engineering, Finance, Compliance, HR routes under /solutions. - Hoist shared LandingShell/HeroCta/Logos to components/ (top-level = shared); refactor hero to consume them. - Restructure all of (landing) to the workspace folder-per-component convention (each component in its own folder + index.ts barrel). * refactor(landing): convert hero-visual CSS-module keyframes to Tailwind Move the hero-visual + stage-home keyframe animations out of CSS modules into tailwind.config (matching the existing dash-animation pattern) and delete both module.css files. Components now use animate-hero-* utilities + arbitrary properties for the per-element delays, SVG stroke draw, and gradient shimmer; reduced-motion preserved via motion-reduce: variants. Upgrade the shimmer's hardcoded #b4b4b4 to the --text-subtle token. brand-tokens.module.css is intentionally kept: it reassigns --surface-*/ --text-* token VALUES via a doubled-class selector for specificity over .light, which Tailwind utilities cannot express. * refactor(landing): move brand palette from CSS module into LandingShell Replace brand-tokens.module.css with a BRAND_TOKENS constant of Tailwind arbitrary-property utilities applied on the LandingShell wrapper, so the brand hex lives in the component, not a stylesheet. They emit in the utilities layer and override .light (@layer base) by cascade order — verified the brand --text-primary (#121212) wins over .light (#1a1a1a). No more .module.css files remain in the landing. * chore(landing): remove Testimonials from the home page for now Drop <Testimonials /> from the landing composition (component kept for re-adding later). * feat(landing): hero send→loader→workflow animation + landing WIP Hero visual: clicking send zooms into the button, morphs the disc into the gooey thinking loader (held, then cycling), slides it straight across to a phrase indicator with the camera following (no zoom-out), then zooms back out as the reply types and the chat morphs into the GitHub→Agent→Jira workflow. The chat card holds a fixed size through the zoomed scene and the greeting reserves its space, so nothing drifts; the user bubble reveals only on zoom-out. Loader ink tweens dark→gradient via the thinking-loader stop-color/flood-color transition. Also folds in in-progress landing work: knowledge + integrations feature callouts, CTA chat, mothership + line-glyph, wordmark tweak; removes the ethos and testimonials sections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): responsive pass for iPad + mobile Make the landing page fully responsive while keeping the desktop layout byte-identical (desktop classes stay the unprefixed baseline; smaller screens layer max-* overrides on top). - Navbar: hide desktop clusters below lg, add MobileNav hamburger sheet (scroll-lock, Escape/tap close, reduced-motion aware) - Hero: collapse the absolute split (visual + logos) to a stacked column below xl so iPad-landscape avoids the headline/visual collision - Mothership: 4-col grid steps to 2 (tablet) then 1 (phone) - Features: drop the floating callout below md, show the un-masked backdrop preview full-width - CTA + Footer: scale type/padding; footer 7-col steps to 3 then 2 - Document the breakpoint strategy in the landing CLAUDE.md Also includes the in-progress mothership goo/iso brand marks and the marks-lab preview route the section depends on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): align hero visual panel to text + logos extent Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(landing): delay hero user bubble until card finishes expanding The grey user bubble's fade-in raced the card's upward grow on send. Hold the bubble's reveal until after the parent-driven grow settles so the card expands fully before the bubble appears. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(landing): isolate new landing — remove dead old-folder code + --landing-* coupling - Delete dead (landing) auth-modal (a duplicate of (home)'s, still on the old --landing-* / dark tokens) — its removal drops the new landing's last styling tie to the old landing. - Delete 5 merge-orphaned, zero-consumer callouts (deploy-callout, mothership-chat-callout, mothership-chat-preview, workflow-graph-preview, model-picker-preview). - Relocate the one live preview (logs-table-preview) into its consumer features/components/ + add a barrel; dissolve the owner-less feature-callouts/ shell. - Fix stale --landing-bg-surface reference in landing-preview-mount. (landing) now has zero (home) imports and zero --landing-* token usage. * refactor(landing): token-map hex, fix a11y/SEO, align structure Styling (within (landing)): - Replace ~90 hardcoded hex colors with the in-scope brand tokens they already equal (--surface-*/--text-*/--border*); divider edges -> --border, field/card edges -> --border-1. Delete the redundant C color-palette mirrors in the landing-preview home/sidebar and route them through tokens. - Convert static inline SVG styles (display:block/outline:none) to Tailwind. - 6 un-tokenizable hexes remain (dark send-button fills, status-green dot) — no brand token exists; left as-is. a11y / SEO: - Decorative mothership goo/iso marks: role='img'+aria-label -> aria-hidden. - Preview chrome titles <h1> -> <span> (kills duplicate client-only H1s). - sitemap.ts: add /workflows and the five /solutions/* routes. Structure: - Folder the bare logo-mark/mobile-nav leaves + barrels; complete the navbar components barrel and consolidate navbar.tsx to a single barrel import. * style(landing): restore taller hero panel with border-shadow chip chrome - Revert the right visual panel to the previous full-height framing (top-8 bottom-8) — hero text (pt-[112px]) and the logos panel are untouched, so their positions and spacing are unchanged. - Apply the canonical border-shadow chip surface: --surface-2 fill + the shared chipBorderShadowRing (1px hairline ring + soft drop shadow) from emcn, the documented chrome for a landing media panel. * feat(landing): swap Volvo for thinkproject and reposition hero logos - Replace Volvo with the thinkproject wordmark (official SVG, tagline/descriptor cropped out, all paths unified to --text-primary #1a1a1a; aspect 6.01). - Reorder the shared 6-logo set so the 3x2 hero grid reads: Rivian|VW (top-left), eXp Realty (top-center), Russell (top-right); Artie (bottom-left), thinkproject (bottom-center), Mobile Health (bottom-right). - Enlarge Rivian|VW a touch (height 15 -> 17, same aspect). - eXp Realty, Artie, Russell, Mobile Health, Rivian|VW all retained. * style(landing): size hero description with the type scale (text-lg) Replace the arbitrary text-[20px]/text-[16px] on the hero description with named scale tokens — text-lg (18px) desktop, text-md (16px) on phones — a touch smaller and the canonical lead size (1.2x the platform's 15px base). * style(landing): hero headline "for AI automations" with break after "agent" Replace "solving automations" with the higher-intent "AI automations" and move the line break after "agent" so "for AI automations." sits on the second line. * style(landing): unify CTA radius and box hero logos in cards - HeroCta email bar: rounded-[13px] -> rounded-lg, so the bar, the inset Book-a-demo chip, the Sign-up chip, and the navbar chips all share one radius. - Hero logos: box each wordmark in a bordered --surface-1 card (platform card chrome: rounded-lg + --border-1, 100px tall) on a responsive 3-up grid (2-up on phones) at a consistent gap-5 rhythm. Wide marks scale to fit (max-w-full h-auto). The platform/solutions 'row' layout stays bare wordmarks. * style(landing): concentric CTA bar radius + tighter logo cards - HeroCta email bar back to rounded-[13px] (= inner chip 8px + ~5px inset) so the Book-a-demo chip's right corners nest concentrically inside the bar. - Logo cards: smaller and tighter — h-20 (80px), px-4, gap-3 (12px, the product UI card-grid rhythm). * style(landing): restore 100px logo cards, scale icons down 15% The 80px cards read too wide-for-their-height. Restore h-[100px] (keeping the tighter gap-3/px-4) and instead shrink the wordmarks to 0.85x their optical size in the grid via GRID_ICON_SCALE — row layout unchanged. * style(landing): match sign-up radius to email bar + shrink logo icons - Sign-up chip overridden to the email bar's rounded-[13px], so the two hero CTAs share one corner radius. - Logo icons: GRID_ICON_SCALE 0.85 -> 0.65 and card padding px-4 -> px-2; card dimensions (h-[100px], gap-3) unchanged. * style(landing): shrink hero logo cards Cards read massive — too tall (100px) and stretched to fill the panel. Drop to h-16 (64px), cap width at w-[150px], and make the grid w-fit so it hugs the cards instead of stretching. gap-3 and the 0.65 icon scale unchanged. * style(landing): upscale hero logo cards ~25% Cards read too small. Bump all dimensions: h-16->h-20 (80px), w-[150px]->w-[180px], px-2->px-3, and icon scale 0.65->0.8. Grid stays content-hugging at gap-3. * style(landing): taller logo cards, larger icons, reorder top row - Card height h-20 -> h-[88px] (width w-[180px] unchanged), icon scale 0.8 -> 0.85. - Top row reordered: eXp (left), Russell (center), Rivian|VW (right). * style(landing): more card height, swap top-row Rivian/eXp back - Card height h-[88px] -> h-24 (96px); width unchanged. - Top row: Rivian|VW (left), Russell (center), eXp (right). * feat(landing): add "Trusted by technical teams at" label above hero logos Top-left, gap-3 above the logo grid (matching the grid rhythm); text-sm (navbar text size) in --text-muted (the label token). * style(landing): recolor logos to --text-body, match label gap to hero rhythm - Recolor all six customer logo SVGs to #3b3b3b (--text-body light value), so they match the Sim navbar wordmark's color. Landing is light-only, so the hardcoded value always equals var(--text-body). - Trusted-by label gap gap-3 -> gap-[22px] (the hero's description->CTA spacing). * style(landing): scale hero CTA down a hair, drop radius to the nav chip's Sign-up read too round. Take the bar + Sign-up to h-[40px] / rounded-lg (8px, the navbar chip radius), and keep the inset Book-a-demo concentric: h-[2em] + rounded (4px) with a 4px inset (8 = 4 + 4). * style(landing): round Book-a-demo to rounded-md to match the bar curve rounded (4px) read too square next to the bar's rounded-lg (8px). Bump to rounded-md (6px) — echoes the bar's curvature, still inside the 4px inset. * style(landing): match Book-a-demo proportions to the navbar chip Restore h-[2.143em] (the chip's 30/14 height ratio); with px-[0.571em] (its 8/14 padding ratio) and the 16px label, Book-a-demo now shares the navbar chip's exact height/padding/text proportions. * style(landing): equal inset around Book-a-demo (h-[30px]) Button was h-[2.143em] (34.3px) -> only ~1.9px top/bottom vs 4px right inside the bar's 38px inner box (40px minus the 1px border). Drop to h-[30px] (the nav chip height) so it centers to an equal 4px inset on top, bottom, and right. * style(landing): enlarge Book-a-demo to h-[32px], tighten inset to 3px h-[30px] read too small/airy in the bar. Bump to h-[32px] and pr-[4px] -> pr-[3px] so the inset is an equal, snugger 3px on top, bottom, and right. * style(landing): lift hero logos off the bottom again (pb-20) Restore the 80px bottom padding so the logos rest 112px above the section bottom (mirroring the hero text's 112px top) instead of sitting flush with the visual panel's bottom. max-xl:pb-0 keeps the stacked layout tight. * improvement(landing): refine hero and mothership visuals * fix(landing): cap hero fold height so it doesn't stretch on huge monitors The section was min-h-[calc(100vh-62px)], so on very tall displays both absolute panels (top-8 bottom-8) stretched — the visual panel grew gigantic and the bottom-anchored logos sank to the very bottom. Cap the fold at 960px via h-[min(calc(100vh-62px),960px)] (min-height can't be capped by max-height): the whole hero stops growing, panels/logos stay proportioned like a large laptop, and the next section just starts below. Laptops (<=16in) are unaffected; max-xl:h-auto keeps the stacked layout below xl. * refactor(landing): session cleanup — DRY CTA label, drop dead grayscale Final tidy after this session's hero/CTA/logo iteration: - hero-cta: extract the duplicated 16px label knob (px-[0.571em] + text-[16px] + font-size:inherit) into a single CTA_LABEL constant, matching the 'single knob' the TSDoc already describes — used by both Book-a-demo and Sign-up. - logos: remove the grayscale filter (now a no-op — all wordmarks were recolored to a single #3b3b3b), inline the single-use LOGO_GAP_X, and flatten the nested cn() into plain layout ternaries (dropping the now-unused cn import). * improvement(landing): animate mothership illustrations * style(landing): solid-ink branding + hero cursor/loader polish Branding: drop the bespoke BRAND_TOKENS palette and bottom-reveal from LandingShell (use the platform's own light tokens); re-ink the wordmark, logo-mark, and hero loader from the gradient+glow to a solid --text-body so the marks read as one ink with the nav text. Add a `shimmer` prop to ThinkingLoader for a static --text-body label, and stroke the squeeze arcs with the shared gradient. Hero visual: the cursor now enters from below the field and chases the send button live through the zoom (retimed beats, no arrive-then-wait); the greeting fades in gently instead of shimmer-revealing; the click ring becomes a press-dip (hero-cursor-press replaces hero-click-ring and hero-greeting-reveal). Extract BlockHandles so the morphed GitHub card carries a real edge handle in scene space; seed the compose card at its true height; pop the sent bubble in immediately. * improvement(landing): update feature iso-marks to perfected geometry Re-author the four Mothership iso-mark illustrations (Integrate, Ingest, Build, Monitor) on the refined isometric geometry, keeping the existing animation vocabulary intact: hover line-draw plus per-mark auto-motion (integrate float, ingest pulse, monitor panel-separate, build grid-flow). Map the raw exports onto the shared token palette/line weight for consistency and tune per-mark sizes for one optical weight. Build is now pure CSS (grid-flow replaces the RAF wave), so it drops 'use client' and renders as a server component. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): add pricing, privacy, terms, and changelog pages - New public /pricing page: Free/Pro/Max/Enterprise cards with the full comparison breakdown transposed from shared upgrade data + JSON-LD; prices, CTAs, and features derive from shared billing constants so they can't drift from the in-app upgrade page. - Migrate /privacy, /terms, and /changelog into the (landing) route group via a shared prose-page system (single source of truth for legal/prose chrome). - Landing polish: solid-ink iso-mark illustrations + footer/cta/features/ mothership spacing and token cleanups; sitemap adds /pricing. - Audit pass: crawlable ChipLink CTAs, correct heading hierarchy, structured-data featureList derived from the visible comparison data, legal plan name Team->Max. * large edits across landing finalization * feat(auth): port OAuth-only signup + Microsoft provider from staging Align auth-page logic with origin/staging (PR #5073) while keeping the new chip-styled UI: - Add Microsoft as a better-auth social sign-in provider (auth.ts) and surface it through the OAuth provider checker, providers API + contract, login/signup forms, SocialLoginButtons, and the landing auth modal. - Gate email/password signup behind the emailSignupEnabled server flag (DISABLE_EMAIL_SIGNUP) so signup becomes OAuth-only when configured. - Add DISABLE_MICROSOFT_AUTH / DISABLE_EMAIL_SIGNUP env + feature flags. * fix(icons): render brand icons legibly when bare and on light tiles (#5292) Monochrome brand icons hardcoded a single white or black fill matched to their colored tile, so they vanished when rendered bare on the home Suggested actions list (white-on-white in light mode, black-on-black in dark mode). Convert those marks to currentColor so they adapt to context, and make tile foregrounds contrast-aware via getTileIconColorClass instead of a hardcoded text-white. Also centralize all color math in apps/sim/lib/colors (perceived brightness, hex/rgb/hsl conversion, contrast-text) and route every consumer through it: the bare-icon audit, block tiles, logs trace view, whitelabeling theming, workspace presence, and the PPTX renderer no longer carry duplicate copies. Adds a bare-icon CI audit (scripts/check-bare-icons.ts) and authoring guidance. --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Waleed <walif6@gmail.com> |
||
|
|
ca0a7ff0c2 |
feat(rich-markdown-editor): live media embeds + shared embed detection util (#5290)
* feat(rich-markdown-editor): live media embeds + shared embed detection util - Extract getEmbedInfo/EmbedInfo into pure @sim/utils/media-embed (carries the PR #5288 dropbox host-validation hardening); repoint the note block to it - Add LinkEmbed: a ProseMirror widget-decoration plugin that renders media players (YouTube, Vimeo, Spotify, Dropbox, …) beneath standalone links in the rich markdown editor, in both editing and read-only surfaces. The document stays a plain markdown link, so markdown round-trips stay lossless - Gate embeds behind an opt-in flag (on for the file editor, off for modal fields) - Polish the knowledge chunk editor to the file editor's centered reading frame while keeping it plaintext for exact embedding fidelity * fix(media-embed): gate provider detection on parsed hostname Validate each platform against the URL's parsed host before extracting, so a look-alike host (youtube.com.evil.com) or a provider domain in the path (evil.com/youtube.com/...) can no longer render a trusted-looking embed. Dropbox is no longer a special case — all providers share the hostMatches gate. Also consolidates the five Spotify branches and orders Twitch clip before channel. * fix(rich-markdown-editor): unique widget key per duplicate embed URL Key embed widgets by source + per-source occurrence index so two standalone links to the same URL render as two distinct players instead of collapsing into one, while keeping the key stable across unrelated edits (no iframe reload). * refactor(media-embed): tighten comments and drop a redundant guard - Drop the redundant paragraph type-check in getStandaloneLinkHref (the caller already filters to paragraphs) and rename the param for clarity - Remove an inline comment and a TSDoc sentence that restated logic documented elsewhere |
||
|
|
4298e577e5 |
fix(workflow-renderer): validate dropbox host in note embed renderer (#5288)
* fix(workflow-renderer): validate dropbox host in note embed renderer
Replace the bare url.includes('dropbox.com') check with a parsed-hostname
match so attacker-controlled hosts (dropbox.com.evil.com, evil.com/?dropbox.com)
no longer get treated as direct dropbox videos. Resolves CodeQL
js/incomplete-url-substring-sanitization (#430).
* fix(workflow-renderer): rewrite dropbox embed via parsed URL, tolerate scheme-less links
Derive the direct video URL from the parsed URL object (rewrite hostname to
dl.dropboxusercontent.com for any dropbox.com/*.dropbox.com host) instead of a
www-only string replace, and accept scheme-less links. Fixes broken embeds for
m.dropbox.com / bare-host links flagged in review.
|
||
|
|
7545391cb3 |
feat(docs): render workflow previews with the shared editor renderer (#5277)
* chore(workflow-renderer): declare @sim/emcn dep + wire the package into docs
Adds the missing @sim/emcn peer/dev dependency to @sim/workflow-renderer (it imports @sim/emcn in every View but resolved only via workspace hoisting). Wires apps/docs to consume @sim/workflow-renderer (dependency, transpilePackages, Tailwind @source) and adds remark-breaks (pulled transitively via the barrel's NoteBlockView export) — mirroring the @sim/emcn integration. Foundation for migrating the docs workflow-preview fork onto the shared Views. Build resolves the package/@source/remark-breaks cleanly.
* feat(docs): render loop/parallel containers with the shared SubflowNodeView
Replaces the forked PreviewContainerNode with a thin DocsContainerNode that maps the static preview data to SubflowNodeView's read-only (isPreview) props — no stores or hooks. Adds the block size to the preview node data so the view can size itself, and corrects the parallel example's start-edge handle id to 'parallel-start-source' (the view derives the handle id from kind). Deletes preview-container-node.tsx. Container colors/icons are now owned by the shared view (loop=blue, parallel=yellow).
* feat(docs): render block nodes with the shared WorkflowBlockView
Replaces the forked PreviewBlockNode with a thin DocsBlockNode that maps the static preview data to WorkflowBlockView's props — store-free, builds the subblock rows (condition/router Context+routes/default + tools + error) via SubBlockRowView, strips branch-id prefixes so the view's regenerated handle ids match, remaps router->router_v2, and keeps the framer-motion dim/stagger wrapper. Promotes resolveIcon into block-icons.tsx, adds the --workflow-edge token to docs global.css, deletes preview-block-node.tsx. The canvas diagrams now render with the real editor's view.
* refactor(workflow-renderer): make editor-only WorkflowBlockView props optional
The child-deploy, schedule, and webhook badge props (and their callbacks) only matter in the editor. Mark them optional and optional-chain the three callbacks so read-only consumers (docs, academy) can omit the whole group instead of passing ~18 explicit off-values. The editor still passes them, so its behavior is byte-identical (verified: apps/sim type-check clean). DocsBlockNode drops the off-props.
* feat(docs): replace how-it-runs static diagrams with live WorkflowPreview
Swaps the four static PNGs on the how-it-runs page for live, app-styled WorkflowPreview diagrams (concurrency, combination, condition+router branching, error path). Adds the four example workflows and renders error-port edges red to match the editor. The English page only; the translated execution/basics pages keep the PNGs.
* refactor(workflow-renderer): the view owns condition/router/error rows
Both the editor container and the docs adapter hand-built the condition/router/error summary rows in an order that had to stay in lockstep with the view's absolute handle-offset math — a three-way coupling with nothing enforcing it. The view now renders those rows itself from the conditionRows/routerRows it already receives (plus a routerContextValue prop for the router's Context row), so row order and handle geometry live together in one place. Both containers pass only data and their non-branch rows.
Editor is byte-identical: getDisplayValue moves to where conditionRows/routerRows are built; the no-subBlock SubBlockRow path is already an exact SubBlockRowView(title, value) passthrough; the error row stays gated on shouldShowDefaultHandles. Verified apps/sim type-check clean. Docs now also renders the error row on condition/router blocks, which the real editor already did (shouldShowDefaultHandles is true for them) — an alignment fix.
* refactor(docs): drop the parallel --wp-* token layer for the app/emcn tokens
The workflow-preview ran a 25-token --wp-* mirror (22 were pure aliases of app tokens docs already defines) plus a .wp-scope wrapper class. Replaces every var(--wp-X) with its canonical app/emcn token (--wp-edge->--workflow-edge, --wp-highlight->--brand-secondary, badges->--badge-*, etc.), adds the one missing token (--divider), and deletes the .wp-scope blocks + class. Visually identical (aliases resolve to the same values); the preview now inherits the same design tokens as the shared views and the rest of the app instead of a hand-rolled parallel set.
* refactor(docs): adopt emcn Badge + dedup resolveIcon in workflow-preview
output-bundle's hand-rolled type badge (BADGE_COLORS + a styled span) becomes the emcn Badge (its green/blue/orange/purple/gray variants use the identical --badge-* tokens). resolveIcon, which had three copies, is now imported once from block-icons by output-bundle and block-inspector.
* refactor(docs): rebuild the preview inspector on emcn chip primitives
The lightbox inspector was a hand-rolled facsimile (raw divs + a CONTROL class string + inline dashed borders). It now composes from the same @sim/emcn primitives the live editor's sub-block controls wrap — ChipSelect/ChipInput/ChipTextarea(viewOnly)/ChipSwitch/ChipTag/FieldDivider/Label — so it reads as the real editor panel, fed example data (read-only, full opacity via readOnly/viewOnly, not greyed). Slider stays minimal (no emcn equivalent) but on app tokens. Props API and embedded/standalone modes unchanged.
* refactor(docs): render the block-reference hero through the shared View
Retires the hand-rolled BlockCard (a parallel reimplementation of WorkflowBlockView) and the BlockDisplaySpec data model. Each block hero is now a single-block PreviewWorkflow (block-display-workflows.ts) rendered through the same toReactFlowElements -> DocsBlockNode -> WorkflowBlockView pipeline as the diagrams, mounted in a minimal fitView ReactFlow (maxZoom 1.3, no canvas chrome). A single block can no longer drift from the canvas.
* fix(docs): define sim's type scale + align the preview inspector to the editor
Docs Tailwind v4 never defined sim's custom font sizes (text-small/caption/md/micro), so emcn components (Label, Badge, the shared views) fell back to inherited sizes — the inspector labels rendered huge. Adds the type scale to the docs @theme. Also aligns the inspector header to the real editor panel (surface-4 bar, size-[18px] rounded-sm icon, text-sm name) and removes the Connections section (and its now-dead prop/wiring).
* fix(docs): inspector shows the full field list + dragged positions persist
Inspector: shows the block type's full field list (from the reference data) with the example's values overlaid, so it reads like the editor panel instead of only the canvas summary rows. Drag: selecting another block no longer relayouts the canvas — node positions the viewer dragged are preserved across highlight/selection changes (only a different workflow relayouts).
* feat(docs): highlight <> references + env vars; hide Ask AI over the lightbox; respace blocks
Inspector text fields render the value with <...> block references and {{...}} environment variables highlighted in brand-secondary (a lean read-only port of the editor's formatDisplayText), in the canonical chip field chrome. The floating Ask AI widget is hidden while a preview lightbox is open. Plus the example-data respacing so the editor-faithful Error row no longer makes stacked blocks overlap.
* fix(docs): make per-type field templates match the real block registry
Audited every block type's field list (the source the inspector + block-reference heroes render) against apps/sim/blocks/blocks/*. Corrected drift to the registry's default-visible fields, titles, and order: agent gains Temperature; router gains Model; wait gains Async; schedule rewritten (default is Daily, not minutes); webhook_trigger expanded to its real default-visible set; human_in_the_loop notification title fixed. Provider-credential and advanced-mode fields stay hidden, matching the editor. Canvas diagrams keep their clean curated rows; the inspector now shows the full, real field list per the chosen clean-canvas/full-inspector split.
* improvement(docs): taller default preview height so respaced diagrams aren't shrunk
Bumps the default WorkflowPreview height 260->300 (the respaced, editor-faithful blocks are taller, so fitView was shrinking diagrams that relied on the default). The tall how-it-runs routing diagram gets 400.
* improvement(docs): zoomable inline preview + taller default + themed controls
The inline preview is now zoomable outside the lightbox: adds react-flow zoom/fit Controls (themed to the dark canvas chrome) and enables pinch-zoom, while keeping scroll-zoom off so the page still scrolls over the diagram. Pan-drag and click-block-to-inspect already worked. Default height 300->340.
* improvement(docs): click canvas to expand; click empty lightbox to deselect
Clicking the inline preview canvas opens the full lightbox; clicking empty space in the lightbox clears the selection, matching the real editor.
* improvement(docs): reveal inline zoom controls on hover only
The always-visible zoom controls felt heavy on the inline preview; they now fade in on hover (matching the expand button) and stay visible in the lightbox.
* improvement(docs): drop zoom controls on the inline preview
Inline preview keeps pinch-zoom, pan, drag, and click-to-expand; zoom buttons stay in the lightbox only.
* improvement(docs): remove zoom controls from the lightbox too
Both previews zoom via scroll/pinch and pan via drag; no on-canvas zoom buttons. Drops the Controls import and its theming CSS.
* improvement(docs): match the real canvas — flat background + editor edge geometry
Closes the last faithfulness gaps the audit found: removes the dot grid (the real editor hides its background — flat bg), aligns PreviewEdge to the editor's smoothstep math (borderRadius 8, offset 30) and 2px stroke (default + error edges), the selection ring to 1.75px, and minZoom to 0.1. Structural parity (blocks/handles/containers/colors/tokens) was already shared. Kept PreviewEdge rather than swapping to WorkflowEdgeView, which would clobber the docs-only highlight/dim/animate for no visual gain.
* improvement(docs): rebrand the docs assistant as 'Ask Sim', styled like the real chat input
Renames the floating assistant from 'Ask AI' to 'Ask Sim' (matching the platform's voice — you talk to Sim) and restyles the composer to mirror the home chat input: a rounded-2xl bordered field with the toolbar inside, and the same 28px circular send/stop button (the home's exact active/disabled colors + white/black arrow). Updates the lightbox hide-selector to the new label.
* improvement(docs): match Ask Sim message styling to the mothership chat
Aligns the user bubble (rounded-[16px] surface-5, text-base/primary, leading-23, max-w-85%) and the assistant markdown (text-base, 600 headings/strong, text-primary dashed-underline links, surface-5 code blocks) to the real mothership chat's user-message + chat-content treatment, instead of the prior generic text-sm rendering. The composer already mirrors the home user-input (rounded-2xl field + 28px circular send button).
* improvement(docs): compact single-row Ask Sim composer
The two-row layout left a tall dead gap (the docs widget has no toolbar buttons to fill the second row). The composer is now a single row — textarea with the circular send button inline — so it sits at the natural input height.
* fix(docs): pass the router Context value to the shared view
DocsBlockNode never set routerContextValue, so the view (which renders the router's Context row from that prop, not from rows) showed a blank Context even when the preview data authored a value like <start.input>. Extract it from the block's Context row and pass it through.
* fix(docs): don't apply a block-type field template that doesn't match the block
inspectorFieldsFor keyed the full field template purely off block.type, but some types are reused across roles (a table action block vs the table trigger, a webhook trigger vs the webhook action), so the wrong template was applied. Only use the template when the block's authored rows are actually a subset of it; otherwise fall back to the block's own rows.
* fix(docs): connect preview edges to subflow container handles
toReactFlowElements hardcoded targetHandle to 'target' and defaulted source handles to 'source', but Loop/Parallel containers (SubflowNodeView) expose a 'loop-end-source'/'parallel-end-source' output handle and a left input handle with no id. Edges into and out of containers therefore failed to connect. Resolve each edge end to the block's real handle based on whether it's a container.
* fix(docs): don't expand the inspector template for blocks with no rows
block.rows.every(...) is vacuously true for an empty rows array, so a block defined only by branches (e.g. a router in ROUTING_WORKFLOW) inherited the type template's invented field defaults. Require non-empty authored rows before applying the template.
* fix(docs): render blank branch/router-context values as '-' like the editor
The editor maps condition/router branch values and the router Context through getDisplayValue, which renders '-' for a blank value. DocsBlockNode mapped them to an empty string, so else branches and unset routes looked blank instead of matching the editor. Mirror getDisplayValue's empty-value handling.
* fix(docs): show '-' for blank inspector branch values, matching the canvas
inspectorFieldsFor passed raw branch.value into the lightbox branch fields, so an unset else route read blank in the inspector while DocsBlockNode (and the editor's getDisplayValue) render '-' on the canvas. Normalize the same way; drop the now-redundant placeholder.
|
||
|
|
c7bb37d488 |
feat(workflow-renderer): extract pure WorkflowBlockView + SubBlockRowView (#5267)
* feat(workflow-renderer): extract pure SubBlockRowView from the block's summary row Splits the canvas block's collapsed subblock summary row into a pure SubBlockRowView (title + resolved displayValue + monospace flag) in @sim/workflow-renderer. The ~9 selector-name hydration hooks stay in the SubBlockRow container behind its memo comparator; the view receives only resolved strings. Byte-identical row JSX. First step toward the full WorkflowBlockView. * feat(workflow-renderer): extract pure WorkflowBlockView shell Moves the canvas block's render (header, badges, dynamic handles, ring) into a pure WorkflowBlockView in @sim/workflow-renderer. WorkflowBlock becomes a thin container that resolves all stores/hooks/permissions, builds the subblock rows + actionBar slots, and binds wouldCreateConnectionCycle (reads the edge store fresh per call to preserve cycle prevention). getHandleClasses/getHandleStyle move into the view; config.icon/bgColor, the webhook provider name, and every visual flag cross as props. Byte-identical JSX — every handle id/class/style/offset and badge guard preserved (verified by an independent adversarial audit). Container drops from 1137 to 829 lines. |
||
|
|
27b2a4f585 |
feat(workflow-renderer): extract edge, subflow, and note Views into @sim/workflow-renderer (#5263)
Adds a @sim/workflow-renderer package with pure, props-driven WorkflowEdgeView, SubflowNodeView, and NoteBlockView shared by the editor and (future) docs preview. Moves block-dimensions constants into the package. Each editor node becomes a thin Container that wires stores/permissions and injects the editor-only ActionBar via a slot. No optimizePackageImports for the workspace component packages (avoids the toast-style module duplication); Tailwind scans the package source. |