Skip to content

Commit a3e287e

Browse files
refactor(dashboards): move authoring guidance to Mothership like Sim Pages
Drop the create-dashboard built-in skill and its rollout gating in the skill lists. Mothership now learns the dashboard format from a sim-dashboards reference in its own research-and-deliverables skill, the pattern Sim Pages use; Sim workspace built-ins stay Agent-block documents. The dashboards flag still gates the viewer and table analytics. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S6aTRnkiu7PxYZPNPXYEMV
1 parent 0958aab commit a3e287e

9 files changed

Lines changed: 13 additions & 112 deletions

File tree

‎apps/sim/app/o/[organizationId]/home/components/composer/composer.tsx‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
} from '@/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor'
2626
import { organizationSkillOptions } from '@/app/workspace/[workspaceId]/home/components/user-input/components/skills-menu-dropdown/organization-skill-options'
2727
import type { ChatRequestMode } from '@/app/workspace/[workspaceId]/home/types'
28-
import { useFeatureFlag } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider'
2928
import type { useFileAttachments } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments'
3029
import { SKILL_CHIP_TRIGGER } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils'
3130
import { getSkillsQueryOptions } from '@/hooks/queries/skills'
@@ -76,7 +75,6 @@ export function Composer({
7675
attachedFilesRef.current = files.attachedFiles
7776
const imagesOnly = requestMode === 'assistant'
7877
const { organization } = useOrganizationContext()
79-
const dashboardsEnabled = useFeatureFlag('dashboards')
8078
const { data: allWorkspaces = [] } = useWorkspacesQuery(!imagesOnly)
8179
const workspaces = (imagesOnly ? [] : allWorkspaces).filter(
8280
(workspace) => workspace.organizationId === organization.id
@@ -90,8 +88,7 @@ export function Composer({
9088
workspaces.map((workspace, index) => ({
9189
...workspace,
9290
skills: skillQueries[index].isPlaceholderData ? [] : (skillQueries[index].data ?? []),
93-
})),
94-
dashboardsEnabled ? [] : ['builtin-create-dashboard']
91+
}))
9592
)
9693
const editor = usePromptEditor({
9794
workspaceId: '',

‎apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/skills-menu-dropdown/organization-skill-options.ts‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import { BUILTIN_SKILLS, isBuiltinSkillId } from '@/lib/workflows/skills/builtin-skills'
22
import type { SkillDefinition } from '@/hooks/queries/skills'
33

4-
/**
5-
* Built-ins are global templates; only user-defined skills carry a workspace. Rollout-gated
6-
* built-ins (the dashboard skill) are excluded the same way the server skill lists exclude them.
7-
*/
4+
/** Built-ins are global templates; only user-defined skills carry a workspace. */
85
export function organizationSkillOptions(
96
workspaces: ReadonlyArray<{
107
id: string
118
name: string
129
skills: readonly SkillDefinition[]
13-
}>,
14-
excludedBuiltinIds: readonly string[] = []
10+
}>
1511
): (SkillDefinition & { workspaceName?: string })[] {
1612
return [
17-
...BUILTIN_SKILLS.filter((skill) => !excludedBuiltinIds.includes(skill.id)).map((skill) => ({
13+
...BUILTIN_SKILLS.map((skill) => ({
1814
...skill,
1915
workspaceId: null,
2016
userId: null,

‎apps/sim/lib/core/config/feature-flags.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ interface FeatureFlagDefinition {
4848
const FEATURE_FLAGS = {
4949
dashboards: {
5050
description:
51-
'Enable .dashboard file rendering, table analytics, and the dashboard authoring skill. Supports global and organization rollout; disabled by default.',
51+
'Enable .dashboard file rendering and table analytics. Supports global and organization rollout; disabled by default.',
5252
fallback: 'DASHBOARDS',
5353
},
5454
'mothership-model-selector': {

‎apps/sim/lib/dashboards/README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Table-backed dashboards
22

3-
A dashboard is a workspace file with MIME type `text/x-sim-dashboard`, handled like Sim pages. Creating or uploading `<Name>.dashboard` is the ingestion signal: the stored name drops the suffix and the content type alone marks the kind, which content updates never reset. It lives in Files and its folders, is created and edited with the ordinary file tools, and shows up in Chat as a file. The Files viewer renders it live. The built-in **create-dashboard** skill documents the syntax. A public share link shows a workspace-only notice and issues no table queries; live public dashboards are deferred.
3+
A dashboard is a workspace file with MIME type `text/x-sim-dashboard`, handled like Sim pages. Creating or uploading `<Name>.dashboard` is the ingestion signal: the stored name drops the suffix and the content type alone marks the kind, which content updates never reset. It lives in Files and its folders, is created and edited with the ordinary file tools, and shows up in Chat as a file. The Files viewer renders it live. Mothership learns the syntax from the `sim-dashboards` reference in its `research-and-deliverables` skill, the same way it learns Sim Pages. A public share link shows a workspace-only notice and issues no table queries; live public dashboards are deferred.
44

55
The implementation has three boundaries:
66

@@ -20,7 +20,7 @@ The toolbar has side-by-side time-range and timezone dropdowns, followed by an i
2020

2121
EMCN is an ECharts theme, so authored `option.color`, series styles, text, and axis styles override its defaults. Standard string axis formatters also override adaptive labels; omit them to get timezone-aware dates and intraday times automatically. Floating tooltips, hover readouts, cursor synchronization, and range selection belong to the framework. Tabs, controls, typography outside the plot, and responsive layout remain EMCN-owned.
2222

23-
Authoring guidance defaults to the muted theme: single-measure panels share a neutral color, comparisons use the shared palette or line patterns, and explicit colors are reserved for user-requested meaning. It contains no example dashboards. Text blocks are optional brief annotations, not viewer instructions or implementation caveats. Chart grids use ECharts 6 outer bounds to fit axis names and end ticks inside the canvas, and horizontal category labels leave space above the configured bar thickness.
23+
The authoring reference defaults to the muted theme: panels rely on the renderer's colors, and explicit colors are reserved for user-requested meaning. Text blocks are optional brief annotations, not viewer instructions or implementation caveats. Chart grids use ECharts 6 outer bounds to fit axis names and end ticks inside the canvas, and horizontal category labels leave space above the configured bar thickness.
2424

2525
## Time and results
2626

@@ -67,5 +67,5 @@ global switch. Local development uses `DASHBOARDS=true` in the app's ignored
6767
environment file.
6868

6969
The server resolves the flag for the canonical workspace organization. It gates
70-
the `.dashboard` viewer, table analytics, and the built-in authoring skill. With
70+
the `.dashboard` viewer and table analytics. The Mothership authoring reference is not flag-gated, like Sim Pages; in an organization without the flag a `.dashboard` file still saves but does not render. With
7171
the flag off, the viewer shows a notice and the YAML stays editable.

‎apps/sim/lib/dashboards/authoring.ts‎

Lines changed: 0 additions & 46 deletions
This file was deleted.

‎apps/sim/lib/skills/application/use-cases.test.ts‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
1414

1515
const { hoisted } = vi.hoisted(() => ({
1616
hoisted: {
17-
flag: vi.fn(),
1817
getById: vi.fn(),
1918
update: vi.fn(),
2019
},
2120
}))
2221

23-
vi.mock('@/lib/dashboards/feature-flag', () => ({ requireDashboardsEnabled: hoisted.flag }))
2422
vi.mock('@/lib/uploads/contexts/workspace', () => workspaceUploadsMock)
2523
vi.mock('@sim/platform-authz/workspace', () => workspaceAuthzMock)
2624
vi.mock('@sim/audit', () => auditMock)
@@ -63,24 +61,12 @@ const skill = {
6361

6462
describe('skill application use cases', () => {
6563
beforeEach(() => {
66-
mocks.flag.mockResolvedValue(undefined)
6764
mocks.loadContext.mockResolvedValue(workspace)
6865
mocks.resolvePermission.mockResolvedValue('read')
6966
mocks.getById.mockResolvedValue(skill)
7067
mocks.update.mockResolvedValue({ ...skill, content: '# Updated' })
7168
})
7269

73-
it('refuses the dashboard authoring skill when dashboards are disabled', async () => {
74-
mocks.getById.mockResolvedValue({ ...skill, id: 'builtin-create-dashboard' })
75-
mocks.flag.mockRejectedValue(new Error('Dashboards are not enabled'))
76-
await expect(
77-
getSkillUseCase.execute({
78-
principal: { kind: 'session', userId: 'reader' },
79-
input: { workspaceId: workspace.workspaceId, skillId: 'builtin-create-dashboard' },
80-
})
81-
).rejects.toThrow('Dashboards are not enabled')
82-
})
83-
8470
it.each(['skill-1', 'builtin-research'])(
8571
'reads %s with read permission in the asserted workspace',
8672
async (skillId) => {

‎apps/sim/lib/skills/application/use-cases.ts‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
ForbiddenOperationError,
1212
} from '@/lib/core/application'
1313
import { OrchestrationError } from '@/lib/core/orchestration/types'
14-
import { isDashboardsEnabled, requireDashboardsEnabled } from '@/lib/dashboards/feature-flag'
1514
import { getSkillActorContext, listSkillEditors, type SkillEditor } from '@/lib/skills/access'
1615
import { skillDelegationPolicy } from '@/lib/skills/application/authorization'
1716
import { skillOperations } from '@/lib/skills/application/operations'
@@ -212,9 +211,6 @@ export const listSkillsUseCase = defineAuthorizedWorkspaceUseCase({
212211
async execute({ input, context }) {
213212
const page = await listSkillSummariesPage({
214213
workspaceId: context.workspaceId,
215-
excludedBuiltinIds: (await isDashboardsEnabled(context.workspaceOrganizationId))
216-
? []
217-
: ['builtin-create-dashboard'],
218214
search: input.search,
219215
sortBy: input.sortBy,
220216
sortOrder: input.sortOrder,
@@ -243,9 +239,6 @@ export const listAvailableSkillsUseCase = defineAuthorizedWorkspaceUseCase({
243239
const skills = await listSkillsForUser({
244240
workspaceId: context.workspaceId,
245241
userId: requirePrincipalSubjectUserId(principal),
246-
excludedBuiltinIds: (await isDashboardsEnabled(context.workspaceOrganizationId))
247-
? []
248-
: ['builtin-create-dashboard'],
249242
})
250243
return { skills }
251244
},
@@ -261,11 +254,6 @@ export const getSkillUseCase = defineAuthorizedWorkspaceUseCase({
261254
resolveContext: ({ input }: { input: GetSkillInput }) =>
262255
resolveSkillContext(input.workspaceId, input.skillId),
263256
authorizationOptions,
264-
async authorizeResource({ context }) {
265-
if (context.skill.id === 'builtin-create-dashboard') {
266-
await requireDashboardsEnabled(context.workspaceOrganizationId)
267-
}
268-
},
269257
async execute({ context }) {
270258
return { skill: context.skill }
271259
},

‎apps/sim/lib/workflows/skills/builtin-skills.ts‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { DASHBOARD_AUTHORING_CONTENT } from '@/lib/dashboards/authoring'
2-
31
/**
42
* Built-in (template) skills that ship with every workspace.
53
*
@@ -192,13 +190,6 @@ You can attach an existing workflow to a table so it runs once for every row, wi
192190
`
193191

194192
export const BUILTIN_SKILLS: readonly BuiltinSkill[] = [
195-
{
196-
id: 'builtin-create-dashboard',
197-
name: 'create-dashboard',
198-
description:
199-
'Build a live table-backed dashboard with YAML, ECharts, time filters, rows, and tabs.',
200-
content: DASHBOARD_AUTHORING_CONTENT,
201-
},
202193
{
203194
id: 'builtin-connect-integration',
204195
name: 'connect-integration',

‎apps/sim/lib/workflows/skills/operations.ts‎

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,10 @@ function builtinSkillRow(workspaceId: string, builtin: BuiltinSkill): SkillRow {
6464
* built-in by sharing its name, and a name that matches the search on the
6565
* built-in matches it on the DB row too.
6666
*/
67-
function visibleBuiltins(
68-
dbNames: Set<string>,
69-
search?: string,
70-
excludedIds: readonly string[] = []
71-
): BuiltinSkill[] {
67+
function visibleBuiltins(dbNames: Set<string>, search?: string): BuiltinSkill[] {
7268
const term = search?.toLowerCase()
7369
return BUILTIN_SKILLS.filter(
74-
(b) =>
75-
!excludedIds.includes(b.id) &&
76-
!dbNames.has(b.name.toLowerCase()) &&
77-
(!term || b.name.toLowerCase().includes(term))
70+
(b) => !dbNames.has(b.name.toLowerCase()) && (!term || b.name.toLowerCase().includes(term))
7871
)
7972
}
8073
export type SkillSortBy = 'name' | 'createdAt' | 'updatedAt'
@@ -186,7 +179,6 @@ export interface SkillSummaryPage {
186179
*/
187180
export async function listSkillSummariesPage(params: {
188181
workspaceId: string
189-
excludedBuiltinIds?: readonly string[]
190182
search?: string
191183
sortBy: SkillSortBy
192184
sortOrder: ListSortOrder
@@ -202,7 +194,7 @@ export async function listSkillSummariesPage(params: {
202194
.orderBy(...listOrderBy(SKILL_SORTS[sortBy], sortOrder))
203195

204196
const dbNames = new Set(dbRows.map((r) => r.name.toLowerCase()))
205-
const builtins = visibleBuiltins(dbNames, params.search, params.excludedBuiltinIds).map((b) =>
197+
const builtins = visibleBuiltins(dbNames, params.search).map((b) =>
206198
builtinSkillSummaryRow(params.workspaceId, b)
207199
)
208200

@@ -231,7 +223,6 @@ export type SkillWithAccess = typeof skill.$inferSelect & { canEdit: boolean }
231223
*/
232224
export async function listSkillsForUser(params: {
233225
workspaceId: string
234-
excludedBuiltinIds?: readonly string[]
235226
userId: string
236227
includeBuiltins?: boolean
237228
workspaceAccess?: WorkspaceAccess
@@ -252,10 +243,8 @@ export async function listSkillsForUser(params: {
252243

253244
// A workspace skill that shares a built-in's name overrides it for everyone.
254245
const dbNames = new Set(tagged.map((r) => r.name.toLowerCase()))
255-
const builtins: SkillWithAccess[] = visibleBuiltins(
256-
dbNames,
257-
undefined,
258-
params.excludedBuiltinIds
246+
const builtins: SkillWithAccess[] = BUILTIN_SKILLS.filter(
247+
(b) => !dbNames.has(b.name.toLowerCase())
259248
).map((b) => ({ ...builtinSkillRow(params.workspaceId, b), canEdit: false }))
260249
return [...builtins, ...tagged]
261250
}

0 commit comments

Comments
 (0)