Skip to content

Commit e23c9cb

Browse files
committed
fix(ci): hash the embedded organization views into the app build
The build task excluded all of platform/ on the claim that the main app imports nothing from it, but app/o bundles platform/embedded and platform/design-system. An edit to either kept the same Turbo hash, so a cached .next could ship a stale UI. Exclude only zone-only paths by name, and do the same in ci:shared so a zone-only edit stops selecting every job.
1 parent 5f4386f commit e23c9cb

3 files changed

Lines changed: 115 additions & 3 deletions

File tree

‎.github/CI.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ WebKit acceptance on Blacksmith uses the matching, digest-pinned Playwright brow
4040

4141
HTTP and docs browser installation first asks the pinned Playwright driver to simulate its OS dependency installation. When the simulation requires no package changes, the runner avoids an apt update; missing dependencies, proposed upgrades, or an unsuccessful simulation use the normal dependency installer. Browser selection and downloads still use the pinned driver, including full native Chromium for extension acceptance.
4242

43+
`app/o` bundles `platform/embedded` and `platform/design-system`, so both are build and `ci:shared` inputs: an edit to either changes the build hash and selects every boundary. The Platform zone (`platform/{app,lib,fixtures,checks,docs,preview,public}` and its own configs) is a separate Next app that the embedded tree may not import, reached only through the `/org` rewrite, and is excluded by name so that a new `platform/` folder stays an input until it is declared zone-only. A zone-only edit selects `platform-design.yml` and the app's unit tests, and no main-app build or acceptance job.
44+
4345
Every HTTP and browser group consumes the same production build in an isolated job/database. Electron also uses that build and skips its development-route warm-up. Local HTTP and Electron development runs retain their dev-server behavior. Build inputs exclude test-only files so a harness edit can reuse an unchanged production build.
4446

4547
Desktop inbox protocol, assistant actions UI, and organization search UI run as three parallel HTTP jobs with isolated databases, apps, and reports. Each harness owns its acceptance task, so an edit selects its own job. The inbox protocol task depends on server and realtime code; the browser tasks also depend on workspace UI. Desktop inbox needs no browser. The UI jobs and browser extension install Chromium while their app starts, waiting for both before executing assertions; browser-install failures still fail the job and preserve the install log. No acceptance assertions are removed by the split.

‎apps/sim/turbo.json‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,26 @@
33
"tasks": {
44
"ci:shared": {
55
"dependsOn": ["^ci:runtime"],
6+
// The Platform zone is a separate Next app with its own workflow (platform-design.yml), so an
7+
// edit that touches only the zone selects no main-app job. `platform/embedded` and
8+
// `platform/design-system` ship inside `app/o`, so they stay inputs. Each zone path is named so
9+
// that a new `platform/` folder is an input until someone decides otherwise.
610
"inputs": [
711
"$TURBO_DEFAULT$",
12+
"!platform/app/**",
13+
"!platform/lib/**",
14+
"!platform/fixtures/**",
15+
"!platform/checks/**",
16+
"!platform/docs/**",
17+
"!platform/preview/**",
18+
"!platform/public/**",
19+
"!platform/next.config.ts",
20+
"!platform/proxy.ts",
21+
"!platform/postcss.config.mjs",
22+
"!platform/package.json",
23+
"!platform/tsconfig.json",
24+
"!platform/README.md",
25+
"!scripts/platform-live-*",
826
"!app/workspace/**",
927
"!app/(landing)/**",
1028
"!app/api/**",
@@ -214,13 +232,28 @@
214232
]
215233
},
216234
"build": {
217-
// next.config bakes the Platform rewrite from SIM_PLATFORM_ORIGIN, and the main app imports
218-
// nothing from its separate `platform/` zone or the seed of its live checks.
235+
// next.config bakes the Platform rewrite from SIM_PLATFORM_ORIGIN. `app/o` bundles
236+
// `platform/embedded` and `platform/design-system`, so those are inputs: leaving them out would
237+
// restore a stale `.next` and ship an old UI. The zone paths below are its own Next app, reached
238+
// only through that rewrite, and the embedded tree imports none of them
239+
// (check:platform-design rule `zone-embedded`, check:boundaries).
219240
"env": ["NEXT_BUILD_SKIP_TYPE_CHECK", "SIM_PLATFORM_ORIGIN"],
220241
"inputs": [
221242
"$TURBO_DEFAULT$",
222243
".env*",
223-
"!platform/**",
244+
"!platform/app/**",
245+
"!platform/lib/**",
246+
"!platform/fixtures/**",
247+
"!platform/checks/**",
248+
"!platform/docs/**",
249+
"!platform/preview/**",
250+
"!platform/public/**",
251+
"!platform/next.config.ts",
252+
"!platform/proxy.ts",
253+
"!platform/postcss.config.mjs",
254+
"!platform/package.json",
255+
"!platform/tsconfig.json",
256+
"!platform/README.md",
224257
"!scripts/platform-live-*",
225258
"!**/*.test.*",
226259
"!**/*.spec.*",

‎scripts/ci-changes.test.ts‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,23 @@ function classifyFiles(files: string[]): Plan {
165165
git(repo, 'commit', '--quiet', '-m', 'change')
166166
return classify(repo, base)
167167
}
168+
/** The app build's real Turbo task hash, the key Docker and CI restore `.next` by. */
169+
function buildHash(repo: string) {
170+
const result = spawnSync('turbo', ['run', 'build', '--filter=@sim/app', '--dry=json'], {
171+
cwd: repo,
172+
encoding: 'utf8',
173+
env: {
174+
...process.env,
175+
PATH: `${resolve(root, 'node_modules/.bin')}:${process.env.PATH}`,
176+
TURBO_TELEMETRY_DISABLED: '1',
177+
},
178+
})
179+
expect(result.status, result.stderr).toBe(0)
180+
const { tasks }: { tasks: { taskId: string; hash: string }[] } = JSON.parse(result.stdout)
181+
const hash = tasks.find((task) => task.taskId === '@sim/app#build')?.hash
182+
if (!hash) throw new Error('turbo planned no @sim/app#build task')
183+
return hash
184+
}
168185
function groups(plan: Plan) {
169186
return [...new Set([...plan.http, ...plan.production].map((suite) => suite.group))].sort()
170187
}
@@ -385,6 +402,66 @@ describe('CI task attribution through real Turborepo', () => {
385402
expect(plan.flags).toMatchObject({ docs: true, docs_site: false, docs_platform: true })
386403
expect(plan.docsGroups).toEqual(['platform'])
387404
})
405+
it.each([
406+
'apps/sim/platform/embedded/app/(shell)/home/home-view.tsx',
407+
'apps/sim/platform/embedded/lib/data/home-model.ts',
408+
'apps/sim/platform/design-system/components/button/button.tsx',
409+
'apps/sim/platform/design-system/components/button/button.module.css',
410+
'apps/sim/platform/design-system/styles/tokens.css',
411+
'apps/sim/app/o/[organizationId]/page.tsx',
412+
])('changes the build hash and selects every boundary for code app/o ships: %s', (file) => {
413+
const { repo } = fixture()
414+
write(repo, file, 'base\n')
415+
git(repo, 'add', '-A')
416+
git(repo, 'commit', '--quiet', '-m', 'seed')
417+
const base = git(repo, 'rev-parse', 'HEAD')
418+
const before = buildHash(repo)
419+
write(repo, file, 'changed\n')
420+
git(repo, 'commit', '--quiet', '-am', 'edit')
421+
expect(buildHash(repo)).not.toBe(before)
422+
const plan = classify(repo, base)
423+
expect(plan.fallback).toBeUndefined()
424+
expect(groups(plan)).toContain('org-redesign')
425+
expect(plan.flags).toMatchObject({ build: true, e2e_build: true, integration: true })
426+
})
427+
it.each([
428+
'apps/sim/platform/app/(shell)/home/page.tsx',
429+
'apps/sim/platform/lib/data/home.ts',
430+
'apps/sim/platform/fixtures/index.ts',
431+
'apps/sim/platform/checks/live/home.spec.ts',
432+
'apps/sim/platform/docs/parity-ledger.md',
433+
'apps/sim/platform/preview/gallery.tsx',
434+
'apps/sim/platform/public/tooltips/one.svg',
435+
'apps/sim/platform/next.config.ts',
436+
'apps/sim/scripts/platform-live-seed.ts',
437+
])('keeps a zone-only edit out of the main app build and its jobs: %s', (file) => {
438+
const { repo } = fixture()
439+
write(repo, file, 'base\n')
440+
git(repo, 'add', '-A')
441+
git(repo, 'commit', '--quiet', '-m', 'seed')
442+
const base = git(repo, 'rev-parse', 'HEAD')
443+
const before = buildHash(repo)
444+
write(repo, file, 'changed\n')
445+
git(repo, 'commit', '--quiet', '-am', 'edit')
446+
expect(buildHash(repo)).toBe(before)
447+
const plan = classify(repo, base)
448+
expect(plan.fallback).toBeUndefined()
449+
expect(groups(plan)).toEqual([])
450+
expect(plan.flags).toMatchObject({
451+
build: false,
452+
e2e_build: false,
453+
integration: false,
454+
desktop_live: false,
455+
})
456+
})
457+
it('keeps a new platform folder an input until it is declared zone-only', () => {
458+
const { repo } = fixture()
459+
const before = buildHash(repo)
460+
write(repo, 'apps/sim/platform/new-shared/thing.ts', 'export const thing = 1\n')
461+
git(repo, 'add', '-A')
462+
git(repo, 'commit', '--quiet', '-m', 'new folder')
463+
expect(buildHash(repo)).not.toBe(before)
464+
})
388465
it('keeps desktop smoke weights out of live runtime attribution', () => {
389466
const plan = classifyFiles(['apps/desktop/e2e-durations.json'])
390467
expect(plan.fallback).toBeUndefined()

0 commit comments

Comments
 (0)