Skip to content

Commit 4bb8674

Browse files
committed
fix(boundaries): hold both data layers to the seam and ratchet views into workspace
Rule 3 watched only platform/embedded/lib after the move, so platform/lib could import a route or a view. Restore it for both, ratcheting the type edges platform/lib already takes from its views, and count embedded views' imports of the workspace surface so that number can only fall.
1 parent 60ff909 commit 4bb8674

4 files changed

Lines changed: 141 additions & 14 deletions

File tree

‎scripts/check-monorepo-boundaries.baseline.json‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,55 @@
4646
"apps/sim/platform/embedded/lib/data/home-model.ts": 1,
4747
"apps/sim/platform/embedded/lib/data/organization-search.ts": 1,
4848
"apps/sim/platform/embedded/lib/data/settings-navigation.ts": 1
49+
},
50+
"viewsToWorkspace": {
51+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/dialogs.tsx": 1,
52+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/page-composer.tsx": 3,
53+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/page-conversation.tsx": 5,
54+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/response-actions.tsx": 1,
55+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/search-input-bar.tsx": 2,
56+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/share-dialog.tsx": 1,
57+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/skeletons.tsx": 1,
58+
"apps/sim/platform/embedded/app/(shell)/_components/conversation/use-page-conversation-effects.ts": 2,
59+
"apps/sim/platform/embedded/app/(shell)/_components/search/search-results.tsx": 3,
60+
"apps/sim/platform/embedded/app/(shell)/_components/search/source-row.tsx": 4,
61+
"apps/sim/platform/embedded/app/(shell)/_components/sidebar/chats-section.tsx": 2,
62+
"apps/sim/platform/embedded/app/(shell)/_components/sidebar/rail-flyout.tsx": 1,
63+
"apps/sim/platform/embedded/app/(shell)/_components/sidebar/sidebar.tsx": 5,
64+
"apps/sim/platform/embedded/app/(shell)/home/_components/home-dashboard/home-dashboard.tsx": 1,
65+
"apps/sim/platform/embedded/app/(shell)/integrations/_components/integrations-page/integrations-route.tsx": 1,
66+
"apps/sim/platform/embedded/app/(shell)/integrations/_components/search-connection-request/search-connection-request.tsx": 1,
67+
"apps/sim/platform/embedded/app/(shell)/search/_components/search-surface/search-surface.tsx": 1,
68+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/admin/add-user-modal.tsx": 1,
69+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/admin/admin-section.tsx": 1,
70+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/connected-accounts/group-providers.tsx": 1,
71+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/integrations/api-key-field.tsx": 2,
72+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/integrations/connector-access.tsx": 1,
73+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/integrations/connector-config-fields.tsx": 3,
74+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/integrations/connector-settings-fields.tsx": 3,
75+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/integrations/source-detail.tsx": 3,
76+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/integrations/source-setup.tsx": 3,
77+
"apps/sim/platform/embedded/app/(shell)/settings/_components/sections/mothership/mothership-section.tsx": 3
78+
},
79+
"zoneLibToViewTypes": {
80+
"apps/sim/platform/lib/data/admin.ts": 1,
81+
"apps/sim/platform/lib/data/chat-composer.ts": 2,
82+
"apps/sim/platform/lib/data/chat-credentials/rows.ts": 1,
83+
"apps/sim/platform/lib/data/chat-draft.ts": 1,
84+
"apps/sim/platform/lib/data/chat-list.ts": 1,
85+
"apps/sim/platform/lib/data/chat-model.ts": 2,
86+
"apps/sim/platform/lib/data/chat-panel.ts": 2,
87+
"apps/sim/platform/lib/data/chat-review.ts": 1,
88+
"apps/sim/platform/lib/data/chat.ts": 1,
89+
"apps/sim/platform/lib/data/credential-groups.ts": 1,
90+
"apps/sim/platform/lib/data/home-model.ts": 1,
91+
"apps/sim/platform/lib/data/home.ts": 2,
92+
"apps/sim/platform/lib/data/search.ts": 2,
93+
"apps/sim/platform/lib/data/settings-navigation.ts": 1,
94+
"apps/sim/platform/lib/data/shell-actions.ts": 1,
95+
"apps/sim/platform/lib/data/shell-context.ts": 1,
96+
"apps/sim/platform/lib/data/shell.ts": 1,
97+
"apps/sim/platform/lib/data/source-settings.ts": 1,
98+
"apps/sim/platform/lib/data/workspaces.ts": 1
4999
}
50100
}

‎scripts/check-monorepo-boundaries.test.ts‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ describe('package boundary command', () => {
210210
["import { Row } from '@/app/o/[organizationId]/row'", false],
211211
["import type { Row } from '@/app/o/[organizationId]/model'", false],
212212
["const row = import('@/app/o/[organizationId]/row')", false],
213-
["import { Row } from '@/app/workspace/[workspaceId]/row'", true],
213+
["import { Row } from '@/app/other/row'", true],
214214
] as const
215215
for (const [source, allowed] of cases) {
216216
writeFileSync(path.join(fixture, file), `${source}\n`)
@@ -261,6 +261,32 @@ describe('package boundary command', () => {
261261
})
262262
})
263263

264+
it('keeps platform/lib from reaching a route, or a view at runtime', () => {
265+
const zoneLib = 'apps/sim/platform/lib/data/widget.ts'
266+
withFixture((fixture, audit) => {
267+
mkdirSync(path.join(fixture, path.dirname(zoneLib)), { recursive: true })
268+
const cases = [
269+
["import { Row } from '@/app/o/[organizationId]/row'", false],
270+
["import type { Row } from '@/app/o/[organizationId]/model'", false],
271+
["const row = import('@/app/o/[organizationId]/row')", false],
272+
["import { Row } from '@/platform/app/(shell)/row'", false],
273+
["export { Row } from '../../app/(shell)/row'", false],
274+
["const row = import('@/platform/app/(shell)/row')", false],
275+
["import { value } from '@/platform/lib/data/value'", true],
276+
["import { useWidgets } from '@/hooks/queries/widgets'", true],
277+
] as const
278+
for (const [source, allowed] of cases) {
279+
writeFileSync(path.join(fixture, zoneLib), `${source}\n`)
280+
const result = audit()
281+
expect.soft(result.status, `${source}\n${result.stderr}`).toBe(allowed ? 0 : 1)
282+
if (!allowed) expect.soft(result.stderr).toContain(`${zoneLib}:1`)
283+
}
284+
writeFileSync(path.join(fixture, zoneLib), "import { Row } from '@/app/o/row'\n")
285+
audit('--update', '--init')
286+
expect(audit().status).toBe(1)
287+
})
288+
})
289+
264290
it('keeps platform/embedded/lib from importing the workspace surface, even for a type', () => {
265291
withFixture((fixture, audit) => {
266292
const cases = [
@@ -342,19 +368,22 @@ describe('package boundary command', () => {
342368
it.each([
343369
['platformToRoutes', VIEW_FILE, '@/app/o/[organizationId]/row'],
344370
['platformLibToWorkspace', LIB_FILE, '@/app/workspace/[workspaceId]/row'],
371+
['viewsToWorkspace', VIEW_FILE, '@/app/workspace/[workspaceId]/row'],
372+
['zoneLibToViewTypes', 'apps/sim/platform/lib/data/widget.ts', '@/platform/app/(shell)/row'],
345373
])('ratchets %s: a new edge fails, and the baseline only shrinks', (ratchet, file, target) => {
346374
withFixture((fixture, audit) => {
375+
mkdirSync(path.join(fixture, path.dirname(file)), { recursive: true })
347376
const baselinePath = path.join(fixture, 'scripts/check-monorepo-boundaries.baseline.json')
348377
const counts = () => JSON.parse(readFileSync(baselinePath, 'utf8'))[ratchet]
349-
const one = `import { a } from '${target}'\n`
378+
const one = `import type { a } from '${target}'\n`
350379
const two = `${one}import type { b } from '${target}'\n`
351380

352381
writeFileSync(path.join(fixture, file), two)
353382
expect(audit('--update', '--init').status).toBe(0)
354383
expect(counts()).toEqual({ [file]: 2 })
355384
expect(audit().status).toBe(0)
356385

357-
writeFileSync(path.join(fixture, file), `${two}import { c } from '${target}'\n`)
386+
writeFileSync(path.join(fixture, file), `${two}import type { c } from '${target}'\n`)
358387
expect(audit().status).toBe(1)
359388
expect(audit('--update').status).toBe(1)
360389
expect(counts()).toEqual({ [file]: 2 })

‎scripts/check-monorepo-boundaries.ts‎

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
* Fix by taking the surface fact (a cursor's route identity, a wire shape) as input from
2121
* the adapter.
2222
*
23-
* 3. The organization surface's seam points one way. `apps/sim/platform/embedded/lib` never imports
24-
* `@/platform/embedded/app` or `@/app/o`, not even for a type: views depend on the data layer, routes
25-
* depend on both, and a data file that reached back into either could not be read or tested
26-
* without it. Fix by moving what both need under `platform/embedded/lib`, or under `lib/` when a surface
27-
* the design system may not reach (the extension, `/workspace`) shares it.
23+
* 3. The organization surface's seam points one way, in both data layers. `apps/sim/platform/embedded/lib`
24+
* never imports `@/platform/embedded/app` or `@/app/o`, not even for a type, and
25+
* `apps/sim/platform/lib` never imports `@/app/o`, not even for a type, or `@/platform/app` at
26+
* runtime: views depend on the data layer, routes depend on both, and a data file that reached
27+
* back into either could not be read or tested without it. Fix by moving what both need under
28+
* the data layer, or under `lib/` when a surface the design system may not reach (the extension,
29+
* `/workspace`) shares it.
2830
* 4. Views (`apps/sim/platform/embedded/app`) read data through `platform/embedded/lib/data`, so a read gets one
2931
* adaptation and one place to fix it: no runtime edge to a data hook, in `@/hooks/queries` or
3032
* a `hooks` folder under `@/ee`. Domain primitives elsewhere in `@/ee` (`lib`, constants,
@@ -44,7 +46,13 @@
4446
* the host app, so an edge from the zone would pull the host into a separate build, and an edge
4547
* the other way would make the zone part of what every organization ships.
4648
*
47-
* Rules 1 to 3 and 7 have no allowlist. Rules 4 to 6 predate their enforcement, so their existing edges
49+
* 8. Views (`apps/sim/platform/embedded/app`) do not import `@/app/workspace`, not even for a type: each
50+
* edge is legacy code that a design-system view cannot be restyled without. Fix by moving the
51+
* shared part under `lib/` or `hooks/`.
52+
* 9. `apps/sim/platform/lib` takes the view models it fills from the views, by type. That makes rule 3
53+
* hold only at runtime for this layer. Fix by moving the model under `platform/lib/data`.
54+
*
55+
* Rules 1 to 3 and 7 have no allowlist. Rules 4 to 6, 8 and 9 predate their enforcement, so their existing edges
4856
* are counted per file in `scripts/check-monorepo-boundaries.baseline.json`, one section per rule. A
4957
* file above its count fails, and so does one below it until the baseline is rewritten, so the
5058
* baseline only shrinks. Regenerate after removing edges with `--update`, which refuses to raise a
@@ -61,6 +69,7 @@ const ROOT = path.resolve(import.meta.dir, '..')
6169
const PACKAGES_DIR = path.join(ROOT, 'packages')
6270
const SIM_DIR = path.join(ROOT, 'apps/sim')
6371
const PLATFORM_DIR = path.join(SIM_DIR, 'platform')
72+
const ZONE_LIB_DIR = path.join(PLATFORM_DIR, 'lib')
6473
const PLATFORM_LIB_DIR = path.join(PLATFORM_DIR, 'embedded/lib')
6574
const PLATFORM_VIEWS_DIR = path.join(SIM_DIR, 'platform/embedded/app')
6675
const EMBEDDED_DIR = path.join(PLATFORM_DIR, 'embedded')
@@ -121,7 +130,7 @@ function applicationViolation(
121130
function viewSeamViolation(specifier: string, bindings: readonly string[] | 'all'): string | null {
122131
if (bindings !== 'all' && bindings.length === 0) return null
123132
if (matchesModule(specifier, '@/hooks/queries') || EE_DATA_HOOK.test(specifier)) {
124-
return `view reads a data hook around platform/lib/data: ${specifier}`
133+
return `view reads a data hook around platform/embedded/lib/data: ${specifier}`
125134
}
126135
return null
127136
}
@@ -276,7 +285,13 @@ async function sourceFiles(dir: string): Promise<string[]> {
276285
return (await allFiles(dir)).filter((file) => !TEST_FILE.test(file))
277286
}
278287

279-
const SEAM_RATCHETS = ['viewDataHooks', 'platformToRoutes', 'platformLibToWorkspace'] as const
288+
const SEAM_RATCHETS = [
289+
'viewDataHooks',
290+
'platformToRoutes',
291+
'platformLibToWorkspace',
292+
'viewsToWorkspace',
293+
'zoneLibToViewTypes',
294+
] as const
280295

281296
type SeamRatchet = (typeof SEAM_RATCHETS)[number]
282297
type SeamCounts = Record<string, number>
@@ -381,6 +396,8 @@ async function main() {
381396
viewDataHooks: [],
382397
platformToRoutes: [],
383398
platformLibToWorkspace: [],
399+
viewsToWorkspace: [],
400+
zoneLibToViewTypes: [],
384401
}
385402

386403
const platformLibFiles = await sourceFiles(PLATFORM_LIB_DIR)
@@ -407,6 +424,29 @@ async function main() {
407424
)
408425
}
409426

427+
const zoneLibFiles = await sourceFiles(ZONE_LIB_DIR)
428+
for (const file of zoneLibFiles) {
429+
offenders.push(
430+
...(await scanImports(file, ({ module, bindings }) => {
431+
const specifier = aliasedSpecifier(file, module.text)
432+
if (matchesModule(specifier, '@/app/o')) return `platform/lib imports a route: ${specifier}`
433+
const runtime = bindings === 'all' || bindings.length > 0
434+
return runtime && matchesModule(specifier, '@/platform/app')
435+
? `platform/lib imports a view at runtime: ${specifier}`
436+
: null
437+
}))
438+
)
439+
seamHits.zoneLibToViewTypes.push(
440+
...(await scanImports(file, ({ module, bindings }) => {
441+
const specifier = aliasedSpecifier(file, module.text)
442+
const typeOnly = bindings !== 'all' && bindings.length === 0
443+
return typeOnly && matchesModule(specifier, '@/platform/app')
444+
? `platform/lib takes a view's type: ${specifier}`
445+
: null
446+
}))
447+
)
448+
}
449+
410450
for (const file of await allFiles(PLATFORM_DIR)) {
411451
offenders.push(
412452
...(await scanImports(file, ({ module }) =>
@@ -415,8 +455,9 @@ async function main() {
415455
)
416456
}
417457

458+
const zoneLibFileSet = new Set(zoneLibFiles)
418459
for (const file of await sourceFiles(PLATFORM_DIR)) {
419-
if (platformLibFileSet.has(file)) continue
460+
if (platformLibFileSet.has(file) || zoneLibFileSet.has(file)) continue
420461
seamHits.platformToRoutes.push(
421462
...(await scanImports(file, ({ module }) => {
422463
const specifier = aliasedSpecifier(file, module.text)
@@ -431,6 +472,14 @@ async function main() {
431472
viewSeamViolation(aliasedSpecifier(file, module.text), bindings)
432473
))
433474
)
475+
seamHits.viewsToWorkspace.push(
476+
...(await scanImports(file, ({ module }) => {
477+
const specifier = aliasedSpecifier(file, module.text)
478+
return matchesModule(specifier, '@/app/workspace')
479+
? `view imports the workspace surface: ${specifier}`
480+
: null
481+
}))
482+
)
434483
}
435484

436485
const baseline = readSeamBaseline()
@@ -459,7 +508,7 @@ async function main() {
459508
)
460509
if (violations.length === 0 && dropped.length === 0) {
461510
console.log(
462-
`✅ Monorepo boundaries OK: no package imports from apps/*, application code is surface-neutral, and platform/lib points away from views and routes (${edgeCount} seam edges baselined)`
511+
`✅ Monorepo boundaries OK: no package imports from apps/*, application code is surface-neutral, the zone and the organization views stay apart, and both data layers point away from views and routes (${edgeCount} seam edges baselined)`
463512
)
464513
return
465514
}

‎scripts/check-platform-design.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ const seamHelpers = new Set([
348348
'lib/sim-search/live/provider-catalog',
349349
'lib/core/config/deployment-shape',
350350
'app/workspace/[workspaceId]/home/components/message-content/components/source-link',
351-
'app/o/[organizationId]/integrations/search-params',
352351
'lib/sim-search/live/policy-schema',
353352
'lib/organization-secrets/validation',
354353
'lib/integrations/credential-display',

0 commit comments

Comments
 (0)