Skip to content

Commit 2b64efa

Browse files
chore: pull deferred quality gates into scope
Fixes the rest of the round-3/4 deferred punch list. Six concrete items; cspNonce option deferred separately (requires library API design + careful emotion-cache plumbing). Library quality gates - `size-limit` budgets in `library/package.json#size-limit`: standalone entry < 220 kB, /react entry < 50 kB, yjs sync chunk < 210 kB (size-limit measures the full transitive graph at runtime — different from Vite's gzip column, but the actual consumer-visible cost). New `pnpm run size` script and CI step in `library-node22-compat`. Verified: 166/29/159 kB current, all under budget. - New `library-react19-compat` CI job in pr-health-checks.yml: installs React 19 + ReactDOM 19 + @types/react@19 + @types/react-dom@19 + @testing-library/react@16 just inside the library workspace, rebuilds, reruns 774 tests. The peer range `^18.3.0 || ^19.0.0` was previously claimed but unverified. Wired into the `pr-health-gate` required-check. Library: rules-of-hooks bug fixes (formerly latent crashes) - `lib/nodes/sfcDiagram/SfcJump.tsx`: hooks `useMemo` (minWidth) + `useEffect` (auto-resize) were called AFTER `if (!width || !height) return null`. If width/height ever flipped, React would see a different number of hooks across renders → crash. Hoisted both hooks above the early return. - `lib/nodes/sfcDiagram/SfcActionTable.tsx`: same pattern, same fix. - `lib/components/popovers/bpmnDiagram/BPMNPoolEditPopover.tsx`: `useState(poolNode.data.name)` called after `if (!poolNode) return null`. Lifted state init above the early return; falls back to "" when the pool node hasn't materialized yet. - Library eslint promotes `react-hooks/rules-of-hooks` back to `error` (warnings only for `exhaustive-deps`, where ~15 hooks have legitimate deliberate-stale-closure patterns). Library public API tightening - Move v2/v3 version-migration helpers out of the public root export into `@tumaet/apollon/internals`: - `convertV2ToV4`, `convertV3ToV4`, `convertV3HandleToV4`, `convertV3NodeTypeToV4`, `convertV3EdgeTypeToV4`, `convertV3MessagesToV4`, `isV2Format`, `isV3Format`, `isV4Format` - All `V3*Typings` (`V3UMLClassifier`, `V3BPMNTask`, etc.) - `importDiagram` remains the public entry — that's what consumers actually use to normalize any version of the model to v4. The rest were accidentally-public internals leaked through the `export * from "./versionConverter"` barrel. - `lib/utils/index.ts` now does `export { importDiagram }` instead of `export *`. Monorepo - `capacitor.config.ts` moved from repo root to `standalone/webapp/capacitor.config.ts`. `webDir` adjusted from "standalone/webapp/dist" to "dist". Verified no `android/` or `ios/` native folders to orphan. All `pnpm capacitor:*` scripts at root now delegate via `--filter @tumaet/webapp`; the docs at `docs/mobile/ios-android-setup.md` continue to work unchanged (they only call `pnpm capacitor:*` from root). - `@capacitor/cli` and `@capacitor/assets` moved from root devDeps to `standalone/webapp/devDependencies` (where `cap` and `capacitor-assets` actually run). - Root `@types/node` now references the catalog. Local feedback loop - `.husky/pre-push` runs `pnpm run lint` before pushes — catches eslint errors before they burn a CI run. - `lint-staged` extended: `*.{ts,tsx,js,jsx,mjs,cjs}` now runs `prettier --write` AND `eslint --fix --no-warn-ignored` on staged files (was prettier-only). Pre-commit feedback in ~1 sec/file instead of waiting for CI. CI hardening (from round-4 server audit) - `pr-health-checks.yml` already had publint+attw added in a prior push; size-limit step + React 19 matrix job round it out.
1 parent 9b9bd23 commit 2b64efa

13 files changed

Lines changed: 5131 additions & 8332 deletions

File tree

.github/workflows/pr-health-checks.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,55 @@ jobs:
199199
# dlx resolves the binary from the package name implicitly.
200200
run: pnpm dlx @arethetypeswrong/cli@0.18.1 attw --pack . --profile esm-only
201201

202+
- name: Enforce bundle-size budget
203+
working-directory: library
204+
# Budgets in library/package.json#size-limit. Fails on regression
205+
# of >10% (size-limit default) per artifact.
206+
run: pnpm run size
207+
208+
# The library declares `react: ^18.3 || ^19` as a peer. Back the upper
209+
# bound with a build + test against React 19 so the range claim is
210+
# actually verified.
211+
library-react19-compat:
212+
needs: [detect-changes]
213+
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.library == 'true'
214+
runs-on: ubuntu-latest
215+
timeout-minutes: 15
216+
steps:
217+
- name: Checkout repository
218+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
219+
220+
- name: Install pnpm
221+
uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
222+
223+
- name: Set up Node
224+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
225+
with:
226+
node-version-file: ".nvmrc"
227+
cache: "pnpm"
228+
229+
- name: Override library devDeps to React 19 via pnpm.overrides
230+
# Run only inside the library workspace; the rest of the
231+
# monorepo stays on React 18.3 (matches Artemis / standalone
232+
# webapp consumers). MUI v6 peer-supports React 19 since 6.0.
233+
run: |
234+
pnpm --filter @tumaet/apollon add -D \
235+
react@19 \
236+
react-dom@19 \
237+
@types/react@19 \
238+
@types/react-dom@19 \
239+
@testing-library/react@16 \
240+
--save-exact --ignore-scripts
241+
242+
- name: Install dependencies
243+
run: pnpm install --frozen-lockfile=false --config.engine-strict=false
244+
245+
- name: Build library against React 19
246+
run: pnpm --filter @tumaet/apollon run build
247+
248+
- name: Run library unit tests against React 19
249+
run: pnpm --filter @tumaet/apollon run test
250+
202251
# Baseline regeneration: see docs/development/visual-tests.md.
203252
visual-regression-tests:
204253
needs: [detect-changes]
@@ -258,6 +307,7 @@ jobs:
258307
detect-changes,
259308
lint-and-format-check,
260309
library-node22-compat,
310+
library-react19-compat,
261311
e2e-tests,
262312
visual-regression-tests,
263313
]

.husky/pre-push

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pnpm run lint

library/eslint.config.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@ export default [
1616
{
1717
settings: { react: { version: "detect" } },
1818
plugins: { "react-hooks": reactHooks },
19-
// react-hooks: surfaced as warnings, not errors, in this turn cycle.
20-
// The rules-of-hooks violations in lib/nodes/sfcDiagram/* are
21-
// pre-existing latent crashes (hooks after an early return) and
22-
// exhaustive-deps drift in ~15 hooks — fixing both is a behavioral
23-
// change that doesn't belong in the foundational pnpm/Node migration.
24-
// Tracked for a follow-up PR.
19+
// rules-of-hooks at error (the pre-existing violations in
20+
// lib/nodes/sfcDiagram/* were latent crashes — hooks called after an
21+
// early return — and are now fixed). exhaustive-deps stays at warn
22+
// because ~15 hooks have legitimate deliberate-stale-closure patterns
23+
// (e.g. imperative editor lifecycle keyed on a single boolean).
2524
rules: {
2625
...reactHooks.configs.recommended.rules,
27-
"react-hooks/rules-of-hooks": "warn",
2826
"react-hooks/exhaustive-deps": "warn",
2927
"react/prop-types": "off",
3028
"no-console": "error",

library/lib/components/popovers/bpmnDiagram/BPMNPoolEditPopover.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ export const BPMNPoolEditPopover = ({ elementId }: PopoverProps) => {
1515
}))
1616
)
1717

18-
const poolNode = nodes.find(
19-
(node) => node.id === elementId
20-
) as Node<BPMNPoolProps>
18+
const poolNode = nodes.find((node) => node.id === elementId) as
19+
| Node<BPMNPoolProps>
20+
| undefined
21+
22+
const [poolName, setPoolName] = useState(poolNode?.data.name ?? "")
2123

2224
if (!poolNode) {
2325
return null
2426
}
2527

26-
const [poolName, setPoolName] = useState(poolNode.data.name)
27-
2828
const handlePoolNameChange = (value: string) => {
2929
setPoolName(value)
3030

library/lib/internals.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
1-
// Unstable surface: Yjs wire-protocol primitives for host integration
2-
// tests. NOT covered by semver. Do not import from application code.
1+
// Unstable surface: collaboration + version-migration primitives for host
2+
// integration tests. NOT covered by semver — do not import from application
3+
// code. Public consumers should use the documented `ApollonEditor` /
4+
// `importDiagram` API.
5+
6+
// Yjs wire-protocol primitives.
37
export { YjsSyncClass, MessageType } from "./sync/yjsSyncClass"
48
export type { SendBroadcastMessage } from "./sync/yjsSyncClass"
59
export { createHeadlessSync } from "./sync/headless"
10+
11+
// Version-migration internals. `importDiagram` (root export) is the public
12+
// entry; everything else here lets a host introspect or test the conversion
13+
// pipeline against a frozen wire format. These names are *internal* and
14+
// may change in any release.
15+
export {
16+
convertV2ToV4,
17+
convertV3ToV4,
18+
convertV3HandleToV4,
19+
convertV3NodeTypeToV4,
20+
convertV3EdgeTypeToV4,
21+
convertV3MessagesToV4,
22+
isV2Format,
23+
isV3Format,
24+
isV4Format,
25+
} from "./utils/versionConverter"
26+
export type * from "./utils/v3Typings"

library/lib/nodes/sfcDiagram/SfcActionTable.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,10 @@ export function SfcActionTable({
2525
}))
2626
)
2727

28-
if (!width || !height) {
29-
return null
30-
}
31-
3228
const actionRows = data?.actionRows || []
3329

34-
// Calculate minimum height based on rows (no header needed)
3530
const minHeight = useMemo(() => {
3631
const rowsHeight = actionRows.length * LAYOUT.DEFAULT_ATTRIBUTE_HEIGHT
37-
// Ensure minimum height for at least one row
3832
return Math.max(rowsHeight, LAYOUT.DEFAULT_ATTRIBUTE_HEIGHT)
3933
}, [actionRows.length])
4034

@@ -59,6 +53,10 @@ export function SfcActionTable({
5953
}
6054
}, [minHeight, height, id, setNodes])
6155

56+
if (!width || !height) {
57+
return null
58+
}
59+
6260
return (
6361
<DefaultNodeWrapper width={width} height={height} elementId={id}>
6462
<NodeToolbar elementId={id} />

library/lib/nodes/sfcDiagram/SfcJump.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,13 @@ export function SfcJump({
2525
}))
2626
)
2727

28-
if (!width || !height) {
29-
return null
30-
}
28+
const fixedHeight = 32 // Fixed height for compact appearance
3129

32-
// Calculate minimum width based on text
3330
const minWidth = useMemo(() => {
3431
const textWidth = measureTextWidth(name || "", LAYOUT.DEFAULT_FONT) + 8
35-
return calculateMinWidth(textWidth, LAYOUT.DEFAULT_PADDING) + 12 // Extra space for the diamond and padding
32+
return calculateMinWidth(textWidth, LAYOUT.DEFAULT_PADDING) + 12
3633
}, [name])
3734

38-
const fixedHeight = 32 // Fixed height for compact appearance
39-
4035
// Auto-expand/shrink width when text changes
4136
useEffect(() => {
4237
if (width && width !== minWidth) {
@@ -60,7 +55,11 @@ export function SfcJump({
6055
}
6156
}, [minWidth, width, id, setNodes])
6257

63-
const finalWidth = Math.max(width ?? 0, minWidth)
58+
if (!width || !height) {
59+
return null
60+
}
61+
62+
const finalWidth = Math.max(width, minWidth)
6463
const finalHeight = fixedHeight
6564

6665
return (

library/lib/utils/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export * from "./diagramTypeUtils"
1212
export * from "./storeUtils"
1313
export * from "./deepPartial"
1414
export * from "./bpmnConstraints"
15-
export * from "./versionConverter"
15+
// Only `importDiagram` is public; the V2/V3 convert helpers and format
16+
// detectors live in `@tumaet/apollon/internals` (internal/unstable).
17+
export { importDiagram } from "./versionConverter"
1618
export * from "./alignmentUtils"
1719
export * from "./requiredInterfaceUtils"
1820
// Deliberately narrow: only the helpers that node SVGs genuinely need to

library/package.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,26 @@
6464
"lint:fix": "eslint --fix .",
6565
"test": "vitest run",
6666
"test:watch": "vitest",
67-
"test:coverage": "vitest run --coverage"
67+
"test:coverage": "vitest run --coverage",
68+
"size": "size-limit"
6869
},
70+
"size-limit": [
71+
{
72+
"name": "standalone entry (peers inlined)",
73+
"path": "dist/index.js",
74+
"limit": "220 kB"
75+
},
76+
{
77+
"name": "/react entry (peers external)",
78+
"path": "dist/react/index.js",
79+
"limit": "50 kB"
80+
},
81+
{
82+
"name": "yjs sync chunk",
83+
"path": "dist/yjsSyncClass-*.js",
84+
"limit": "210 kB"
85+
}
86+
],
6987
"peerDependencies": {
7088
"@emotion/react": "^11.11.0",
7189
"@emotion/styled": "^11.11.0",
@@ -124,6 +142,8 @@
124142
"jsdom": "catalog:",
125143
"react": "catalog:",
126144
"react-dom": "catalog:",
145+
"size-limit": "11.2.0",
146+
"@size-limit/preset-big-lib": "11.2.0",
127147
"typescript": "catalog:",
128148
"typescript-eslint": "catalog:",
129149
"vite": "catalog:",

package.json

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@
3737
"test": "pnpm --filter @tumaet/apollon run test",
3838
"test:e2e": "pnpm --filter @tumaet/webapp run test:e2e",
3939
"prepare": "husky || true",
40-
"capacitor:add:android": "cap add android",
41-
"capacitor:add:ios": "cap add ios --packagemanager CocoaPods",
42-
"capacitor:sync": "cap sync",
43-
"capacitor:open:android": "cap open android",
44-
"capacitor:open:ios": "cap open ios",
45-
"capacitor:assets:generate:ios": "capacitor-assets generate --ios",
46-
"capacitor:assets:generate:android": "capacitor-assets generate --android",
40+
"capacitor:add:android": "pnpm --filter @tumaet/webapp run capacitor:add:android",
41+
"capacitor:add:ios": "pnpm --filter @tumaet/webapp run capacitor:add:ios",
42+
"capacitor:sync": "pnpm --filter @tumaet/webapp run capacitor:sync",
43+
"capacitor:open:android": "pnpm --filter @tumaet/webapp run capacitor:open:android",
44+
"capacitor:open:ios": "pnpm --filter @tumaet/webapp run capacitor:open:ios",
45+
"capacitor:assets:generate:ios": "pnpm --filter @tumaet/webapp run capacitor:assets:generate:ios",
46+
"capacitor:assets:generate:android": "pnpm --filter @tumaet/webapp run capacitor:assets:generate:android",
4747
"package:vscode": "pnpm --filter apollon-vscode run package:vsix"
4848
},
4949
"license": "MIT",
@@ -52,19 +52,18 @@
5252
"pnpm": ">=11.1.0"
5353
},
5454
"devDependencies": {
55-
"@capacitor/assets": "3.0.5",
56-
"@capacitor/cli": "8.3.0",
5755
"@commitlint/cli": "19.6.0",
5856
"@commitlint/config-conventional": "19.6.0",
59-
"@types/node": "24.12.4",
57+
"@types/node": "catalog:",
6058
"concurrently": "9.1.0",
6159
"husky": "9.1.7",
6260
"lint-staged": "15.5.2",
6361
"prettier": "3.4.2"
6462
},
6563
"lint-staged": {
6664
"*.{js,jsx,mjs,cjs,ts,tsx}": [
67-
"prettier --ignore-unknown --write"
65+
"prettier --ignore-unknown --write",
66+
"eslint --fix --no-warn-ignored"
6867
],
6968
"*.{json,md,yml,yaml}": "prettier --ignore-unknown --write"
7069
}

0 commit comments

Comments
 (0)