Skip to content

Commit b4787dd

Browse files
fix(tables): right-align run/stop in embedded toolbar; workflow cells format like normal cells (#4806)
* fix(tables): right-align run/stop in the embedded table toolbar Add a right-aligned `trailing` slot to ResourceOptionsBar and move the embedded mothership table's run/stop control into it, so Filter + Sort stay left-aligned and run/stop sits opposite on the right. No-op for the search-bearing consumers (logs, resource list), which don't pass `trailing`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(tables): workflow-output cells format values like normal cells Workflow-output columns short-circuited in resolveCellRender and rendered their value as plain text, so a sim-resource URL / external URL / JSON / date produced by a workflow never got the chip, favicon link, or typed formatting a normal cell gets. Factor value formatting into a shared `resolveValueKind` helper used by both the workflow-value branch and the plain-cell branch; the workflow branch keeps the typewriter reveal for plain streaming text via a `typewriter` flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(tables): detect resource/URL links on workflow output regardless of column type Workflow output columns default to `json` (columnTypeForLeaf), so routing their values through the type-based formatter (a) gated chip/URL promotion behind `column.type === 'string'` — a URL produced by a json-typed output never became a chip — and (b) JSON.stringify'd plain string values, adding quotes and losing the typewriter reveal. Detect links (sim-resource chip / favicon URL) on the value string directly for workflow outputs, falling back to the plain `value` kind; plain cells keep the type-based formatting. Addresses Greptile P2 on #4806. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 640b7e1 commit b4787dd

3 files changed

Lines changed: 64 additions & 21 deletions

File tree

apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options-bar/resource-options-bar.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ interface ResourceOptionsBarProps {
7373
filterActive?: boolean
7474
filterTags?: FilterTag[]
7575
extras?: ReactNode
76+
/** Right-aligned slot. Unlike `extras` (which sits with the left controls),
77+
* `trailing` is pushed to the far right via `justify-between` — used for the
78+
* table's run/stop control opposite the left-aligned filter/sort. */
79+
trailing?: ReactNode
7680
}
7781

7882
export const ResourceOptionsBar = memo(function ResourceOptionsBar({
@@ -83,9 +87,16 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({
8387
filterActive,
8488
filterTags,
8589
extras,
90+
trailing,
8691
}: ResourceOptionsBarProps) {
8792
const hasContent =
88-
search || sort || filter || onFilterToggle || extras || (filterTags && filterTags.length > 0)
93+
search ||
94+
sort ||
95+
filter ||
96+
onFilterToggle ||
97+
extras ||
98+
trailing ||
99+
(filterTags && filterTags.length > 0)
89100
if (!hasContent) return null
90101

91102
return (
@@ -143,6 +154,7 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({
143154
) : null}
144155
{sort && <SortDropdown config={sort} />}
145156
</div>
157+
{trailing && <div className='flex shrink-0 items-center gap-1.5'>{trailing}</div>}
146158
</div>
147159
</div>
148160
)

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-render.tsx

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ export function resolveCellRender({
7777
// Value wins over pending-upstream: a finished column stays finished even
7878
// while other blocks in the group are still running. An empty string is not
7979
// a value — it falls through so a completed enrichment can show "Not found".
80-
if (!isEmpty) return { kind: 'value', text: stringifyValue(value) }
80+
// A value that's wholly a resource/URL string renders as a chip/link (any
81+
// column type — workflow output is free-form); otherwise the plain `value`
82+
// kind keeps the typewriter reveal for streaming text.
83+
if (!isEmpty) {
84+
const text = stringifyValue(value)
85+
return resolveLinkKind(text, currentWorkspaceId) ?? { kind: 'value', text }
86+
}
8187

8288
if (inFlight && !(groupHasBlockErrors && !blockRunning)) {
8389
// A `pending` cell whose jobId starts with `paused-` is mid-pause
@@ -109,21 +115,7 @@ export function resolveCellRender({
109115
if (column.type === 'date') return { kind: 'date', text: String(value) }
110116
if (column.type === 'string') {
111117
const text = stringifyValue(value)
112-
if (currentWorkspaceId) {
113-
const resource = extractSimResourceInfo(text)
114-
if (resource && resource.workspaceId === currentWorkspaceId) {
115-
return {
116-
kind: 'sim-resource',
117-
workspaceId: resource.workspaceId,
118-
resourceType: resource.resourceType,
119-
resourceId: resource.resourceId,
120-
href: resource.href,
121-
}
122-
}
123-
}
124-
const urlInfo = extractUrlInfo(text)
125-
if (urlInfo) return { kind: 'url', text, href: urlInfo.href, domain: urlInfo.domain }
126-
return { kind: 'text', text }
118+
return resolveLinkKind(text, currentWorkspaceId) ?? { kind: 'text', text }
127119
}
128120
return { kind: 'text', text: stringifyValue(value) }
129121
}
@@ -134,6 +126,45 @@ function stringifyValue(value: unknown): string {
134126
return JSON.stringify(value)
135127
}
136128

129+
/** Returns a `sim-resource` cell kind when `text` is a URL pointing to a
130+
* resource in the current workspace, else null. Shared by plain string cells
131+
* and workflow-output value cells so both surface in-workspace resource links
132+
* as tagged chips. */
133+
function resolveSimResourceKind(
134+
text: string,
135+
currentWorkspaceId: string | undefined
136+
): Extract<CellRenderKind, { kind: 'sim-resource' }> | null {
137+
if (!currentWorkspaceId) return null
138+
const resource = extractSimResourceInfo(text)
139+
if (!resource || resource.workspaceId !== currentWorkspaceId) return null
140+
return {
141+
kind: 'sim-resource',
142+
workspaceId: resource.workspaceId,
143+
resourceType: resource.resourceType,
144+
resourceId: resource.resourceId,
145+
href: resource.href,
146+
}
147+
}
148+
149+
/**
150+
* Promotes a cell value that is wholly a resource/URL string to a chip
151+
* (in-workspace resource) or a favicon link, else null. Shared by plain string
152+
* cells and workflow-output value cells. Workflow outputs apply this regardless
153+
* of `column.type` (their type defaults to `json`, so gating on `string` would
154+
* miss URL outputs); a stringified object never matches the whole-string URL
155+
* check, so it stays JSON/text.
156+
*/
157+
function resolveLinkKind(
158+
text: string,
159+
currentWorkspaceId: string | undefined
160+
): Extract<CellRenderKind, { kind: 'sim-resource' } | { kind: 'url' }> | null {
161+
const simKind = resolveSimResourceKind(text, currentWorkspaceId)
162+
if (simKind) return simKind
163+
const urlInfo = extractUrlInfo(text)
164+
if (urlInfo) return { kind: 'url', text, href: urlInfo.href, domain: urlInfo.domain }
165+
return null
166+
}
167+
137168
const BARE_DOMAIN_RE = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
138169

139170
function extractUrlInfo(text: string): { href: string; domain: string } | null {

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,14 +478,14 @@ export function Table({
478478
}
479479
/>
480480
)}
481-
{/* Sort + filter render in both modes. In embedded (mothership) mode there's
482-
no ResourceHeader, so the run/stop control rides in the options bar's
483-
`extras` slot — keeping the bar populated whether or not a run is live. */}
481+
{/* Sort + filter render in both modes (left-aligned). In embedded (mothership)
482+
mode there's no ResourceHeader, so the run/stop control rides in the options
483+
bar's right-aligned `trailing` slot — opposite the left-aligned filter/sort. */}
484484
<ResourceOptionsBar
485485
sort={sortConfig}
486486
onFilterToggle={() => setFilterOpen((prev) => !prev)}
487487
filterActive={filterOpen || !!queryOptions.filter}
488-
extras={
488+
trailing={
489489
embedded && selection.totalRunning > 0 ? (
490490
<RunStatusControl
491491
running={selection.totalRunning}

0 commit comments

Comments
 (0)