Skip to content

Commit 3019c8e

Browse files
committed
improvement(platform): simplify the CSS audit and the notice bridge
1 parent 5e559b2 commit 3019c8e

3 files changed

Lines changed: 39 additions & 49 deletions

File tree

‎apps/sim/platform/app/(shell)/_components/notification-tray/notification-tray.tsx‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import type { ShellNotice } from '@/platform/app/(shell)/_components/model'
33

44
type Notice = Omit<ShellNotice, 'href'> & { onAction?: () => void }
55

6-
const ERROR_ICON = 'I047'
7-
86
/** The notices a toast is showing, so re-raising or dismissing a notice id reaches its toast. */
97
const toastIdByNotice = new Map<string, string>()
108

@@ -13,12 +11,11 @@ function dismiss(id: string) {
1311
if (toastId) toast.dismiss(toastId)
1412
}
1513

16-
function notify({ id, title, description, icon, action, onAction }: Notice) {
14+
function notify({ id, title, description, action, onAction }: Notice) {
1715
dismiss(id)
1816
const toastId = toast({
1917
message: title,
2018
description,
21-
variant: icon === ERROR_ICON ? 'error' : 'default',
2219
action: action && onAction ? { label: action, onClick: onAction } : undefined,
2320
onDismiss: () => {
2421
if (toastIdByNotice.get(id) === toastId) toastIdByNotice.delete(id)

‎scripts/check-platform-css.test.ts‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ const scoped = async (css: string, from = ENTRY) =>
1313

1414
describe('findEntryEscapes', () => {
1515
it.each([
16-
[':root { --spacing: 1px; }'],
17-
['html { color: red; }'],
18-
['[data-platform-ui] html { color: red; }'],
19-
['.flex { display: flex; }'],
20-
['[data-material="shell"] { color: red; }'],
21-
['@layer utilities { [data-platform-ui] .flex { display: flex; } }'],
22-
['@property --tw-shadow { syntax: "*"; inherits: false; }'],
23-
['@keyframes spin { to { rotate: 1turn; } }'],
24-
['@media (width > 1px) { .flex { display: flex; } }'],
16+
':root { --spacing: 1px; }',
17+
'html { color: red; }',
18+
'[data-platform-ui] html { color: red; }',
19+
'.flex { display: flex; }',
20+
'[data-material="shell"] { color: red; }',
21+
'@layer utilities { [data-platform-ui] .flex { display: flex; } }',
22+
'@property --tw-shadow { syntax: "*"; inherits: false; }',
23+
'@keyframes spin { to { rotate: 1turn; } }',
24+
'@media (width > 1px) { .flex { display: flex; } }',
2525
])('flags %s', (css) => {
2626
expect(findEntryEscapes(css, '')).not.toEqual([])
2727
})
@@ -50,13 +50,13 @@ describe('findEntryEscapes', () => {
5050

5151
describe('findModuleEscapes', () => {
5252
it.each([
53-
['button { color: red; }'],
54-
['[data-platform-ui] { color: red; }'],
55-
[':global(.flex) { display: flex; }'],
56-
['.local, body { color: red; }'],
57-
['@layer base { .local { color: red; } }'],
58-
['@font-face { font-family: "X"; }'],
59-
['@property --x { syntax: "*"; inherits: false; }'],
53+
'button { color: red; }',
54+
'[data-platform-ui] { color: red; }',
55+
':global(.flex) { display: flex; }',
56+
'.local, body { color: red; }',
57+
'@layer base { .local { color: red; } }',
58+
'@font-face { font-family: "X"; }',
59+
'@property --x { syntax: "*"; inherits: false; }',
6060
])('flags %s', (css) => {
6161
expect(findModuleEscapes(css, 'x.module.css')).not.toEqual([])
6262
})

‎scripts/check-platform-css.ts‎

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ const SCOPED = /^(?::where\(html\.dark\) )?(?:\[data-platform-ui\]|:where\(\[dat
3333
const GLOBAL_TARGET = /(?:^|[\s>+~(,])(?::root|:host|html|body)(?![\w-])/
3434
const ENTRY_AT_RULES = new Set(['media', 'container', 'supports', 'font-face'])
3535
const MODULE_AT_RULES = new Set(['media', 'container', 'supports', 'keyframes'])
36-
const KEYFRAMES = /keyframes$/
3736
const ORGANIZATION_ROUTES = path.join('app', 'o', '[organizationId]') + path.sep
3837
const NEXT_ENTRY =
3938
/^(?:page|layout|template|loading|error|global-error|not-found|default|route)\.[jt]sx?$/
4039
const ROOT_ENTRIES = ['proxy.ts', 'instrumentation.ts', 'instrumentation-node.ts', 'bootstrap.ts']
41-
const SOURCE = /\.(?:[jt]sx?|css)$/
40+
const CODE_FILE = /\.[jt]sx?$/
41+
const CSS_MODULE = /\.module\.css$/
4242
const TEST_FILE = /\.(?:test|spec)\.[jt]sx?$|\.integration\.ts$/
4343
const SKIPPED_FOLDERS = new Set(['node_modules', '.next', 'dist', 'public'])
4444
const RESOLVE_SUFFIXES = ['', '.ts', '.tsx', '.js', '.jsx', '.cjs', '/index.ts', '/index.tsx']
@@ -48,11 +48,6 @@ export interface Finding {
4848
detail: string
4949
}
5050

51-
function isInsideKeyframes(node: { parent?: unknown }): boolean {
52-
const parent = node.parent as AtRule | undefined
53-
return parent?.type === 'atrule' && KEYFRAMES.test(parent.name)
54-
}
55-
5651
/** Checks compiled entry CSS. `hostGlobals` is the host stylesheet a new font face must not alter. */
5752
export function findEntryEscapes(css: string, hostGlobals: string): Finding[] {
5853
const findings: Finding[] = []
@@ -93,8 +88,8 @@ export function findModuleEscapes(css: string, file: string): Finding[] {
9388
}
9489
})
9590
root.walkRules((rule) => {
96-
if (isInsideKeyframes(rule)) return
97-
if (rule.parent?.type === 'rule') return
91+
const { parent } = rule
92+
if (parent?.type === 'rule' || (parent as AtRule | undefined)?.name === 'keyframes') return
9893
for (const selector of rule.selectors) {
9994
if (selector.includes(':global') || !/\.-?[_a-zA-Z]/.test(selector)) {
10095
findings.push({ rule: 'module-escape', detail: `${file}: ${selector}` })
@@ -104,12 +99,12 @@ export function findModuleEscapes(css: string, file: string): Finding[] {
10499
return findings
105100
}
106101

107-
function walk(directory: string, files: string[] = []): string[] {
108-
for (const name of readdirSync(directory)) {
109-
if (SKIPPED_FOLDERS.has(name)) continue
110-
const full = path.join(directory, name)
111-
if (statSync(full).isDirectory()) walk(full, files)
112-
else if (SOURCE.test(name) && !TEST_FILE.test(name)) files.push(full)
102+
function walk(directory: string, pattern: RegExp, files: string[] = []): string[] {
103+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
104+
if (SKIPPED_FOLDERS.has(entry.name)) continue
105+
const full = path.join(directory, entry.name)
106+
if (entry.isDirectory()) walk(full, pattern, files)
107+
else if (pattern.test(entry.name) && !TEST_FILE.test(entry.name)) files.push(full)
113108
}
114109
return files
115110
}
@@ -123,35 +118,33 @@ function resolveSpecifier(specifier: string, from: string): string | null {
123118
if (!base) return null
124119
for (const suffix of RESOLVE_SUFFIXES) {
125120
const candidate = `${base}${suffix}`
126-
try {
127-
if (statSync(candidate).isFile()) return candidate
128-
} catch {}
121+
if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate
129122
}
130123
return null
131124
}
132125

133-
function readImports(file: string): string[] {
134-
if (file.endsWith('.css')) return []
135-
return new Bun.Transpiler({ loader: file.endsWith('x') ? 'tsx' : 'ts' })
136-
.scan(readFileSync(file, 'utf8'))
137-
.imports.flatMap(({ path: specifier }) => resolveSpecifier(specifier, file) ?? [])
138-
}
139-
140126
/** Walks importers back from `platform/`, so each module is visited once however the graph cycles. */
141127
function findRouteReach(): Finding[] {
142128
const insidePlatform = (file: string) => file.startsWith(`${PLATFORM}${path.sep}`)
143-
const files = walk(APP).filter((file) => !insidePlatform(file))
129+
const files = walk(APP, CODE_FILE).filter((file) => !insidePlatform(file))
130+
const tsTranspiler = new Bun.Transpiler({ loader: 'ts' })
131+
const tsxTranspiler = new Bun.Transpiler({ loader: 'tsx' })
144132
const importers = new Map<string, string[]>()
145133
const queue: string[] = []
146134
/** Maps a module that reaches `platform/` to its next step on the way there. */
147135
const nextStep = new Map<string, string>()
148136
for (const file of files) {
149-
for (const target of readImports(file)) {
137+
const transpiler = file.endsWith('x') ? tsxTranspiler : tsTranspiler
138+
for (const { path: specifier } of transpiler.scan(readFileSync(file, 'utf8')).imports) {
139+
const target = resolveSpecifier(specifier, file)
140+
if (!target) continue
150141
if (insidePlatform(target)) {
151142
if (!nextStep.has(file)) queue.push(file)
152143
nextStep.set(file, target)
144+
} else if (importers.has(target)) {
145+
importers.get(target)?.push(file)
153146
} else {
154-
importers.set(target, [...(importers.get(target) ?? []), file])
147+
importers.set(target, [file])
155148
}
156149
}
157150
}
@@ -200,7 +193,7 @@ if (import.meta.main) {
200193
})
201194
const findings = findEntryEscapes(entryCss, readFileSync(HOST_GLOBALS, 'utf8'))
202195

203-
const modules = walk(PLATFORM).filter((file) => file.endsWith('.module.css'))
196+
const modules = walk(PLATFORM, CSS_MODULE)
204197
for (const file of modules) {
205198
findings.push(...findModuleEscapes(await compile(file), path.relative(APP, file)))
206199
}

0 commit comments

Comments
 (0)