Skip to content

fix(flip-ui): correct dependency scoping, stop shipping Mirage, and enforce both in CI (#1041) - #1061

Open
atriaybagur wants to merge 7 commits into
developfrom
1041-flip-ui-dependency-scoping
Open

fix(flip-ui): correct dependency scoping, stop shipping Mirage, and enforce both in CI (#1041)#1061
atriaybagur wants to merge 7 commits into
developfrom
1041-flip-ui-dependency-scoping

Conversation

@atriaybagur

@atriaybagur atriaybagur commented Aug 26, 2026

Copy link
Copy Markdown
Member

Closes #1041.

Why

Dependabot derives a security alert's scope label from which stanza a package occupies. Packages that flip-ui ships to users were sitting in devDependencies, so an alert on axios, pinia or aws-amplify would arrive labelled "Development" — reading as not in the production bundle when it is in the bundle make deploy-ui serves through CloudFront. In the #1036 sweep the label happened to hold for all three alerts, but that was luck.

What shipped

21 packages corrected: 18 moved out of devDependencies, 2 that were declared in neither stanza (codemirror, tippy.js — they resolved only through npm hoisting a parent's tree), and husky, which had drifted the other way.

Mirage is no longer in the production bundle. src/main.ts statically imported ../mocks/server. The VITE_LOCAL branch that calls makeServer() folds correctly, but miragejs patches Error.prototype at module scope, so rolldown kept the module and emitted ~230 of them — Mirage, Pretender, route-recognizer, inflected and all of lodash — into the entry chunk of every build. This is the exact FLIP#794 mechanism src/main.ts already documents for mocks/demo-server; that one was fixed with a dynamic import, this one never was.

develop this branch
Entry chunk 331 KB 182 KB (−45%)
Total shipped JS 2,173 KB 2,045 KB (−128 KB)

The rule is now enforced, not just documented. eslint.config.mjs runs import-x/no-extraneous-dependencies over src/ (minus tests) and mocks/, so a devDependency imported from production source fails npm run lint in CI at the moment it is written. mocks/ is deliberately not exempt — mocks/demo-server.ts ships in the public /ark_demo bundle, which is why miragejs and uuid are dependencies regardless of the main-bundle fix.

The Mirage removal itself is enforced too (review follow-up): miragejs is a legitimate dependency — the demo bundle ships it — so no-extraneous-dependencies cannot flag a static re-import of mocks/server. A flip-ui/no-static-mirage no-restricted-imports block now rejects static imports of miragejs/pretender/either mock server from src/ production sources (the rule does not match dynamic import(), so the legal bootstrap() loads pass), and assert-no-demo-artefacts carries Mirage/Pretender library-code sentinels so the artefact check also catches the class no source lint can see — a folded mock branch that stops folding.

Plus the Dependency scoping section in flip-ui/README.md (AC3).

Notes for the reviewer

  • Read package.json and eslint.config.mjs; skim the lock. The lockfile diff is large but contains zero changes to any version/resolved/integrity of a pre-existing package — only "dev": true flag flips, the root stanza move, and the 7 packages of the new lint plugin's tree.
  • @popperjs/core is in dependencies even though tree-shaking currently drops it (its only consumers, AiSelect/AiChipSelect, are unused components). Scoping keys off the import graph, not the bundle: what production code imports is stable, whereas what survives tree-shaking flips whenever an unrelated component starts or stops being used. Over-declaring is the safe direction — it labels an alert "Production" for something that might ship.
  • highlight.js is deliberately untouched. It has no static import — a from "..." grep reads it as dev-only — but it loads via import("highlight.js/lib/core") and does ship. It was already correct.
  • 19 pre-existing lint errors in mocks/ are fixed here, surfaced only because mocks/ entered the lint glob for the first time. 16 were formatting (auto-fixed); the rest stemmed from a stale eslint-disable naming @typescript-eslint/ban-types, a rule removed in typescript-eslint v8 and split into no-empty-object-type — so it had been suppressing nothing.
  • eslint-plugin-import-x rather than eslint-plugin-import: the latter caps at ESLint ^9 and this repo is on ^10.1.0. Published 2026-06-28, well clear of the 72-hour cooldown.

Verification

Check Result
npm ci ✅ no sync error — the real gate; npm ci hard-fails on stanza drift
npm run lint ✅ 0 errors across src/ and mocks/ (3 pre-existing max-len warnings in an untouched file)
Guard works ✅ adversarial test — demoting axios back to devDependencies reddens the lint with 3 errors
Mirage guard works ✅ a deliberate static import of miragejs / ../mocks/server / ../mocks/demo-server reddens the lint with 3 no-restricted-imports errors; a real build:deploy passes with all 7 sentinels absent from 93 dist files
npm run test:unit ✅ 110 files, 1307 passed, 1 skipped (after the second develop merge-back; one fewer than before because #1076 on develop removed the legacy models-redirect case)
npm run build:deploy ✅ builds; check-build-flags + assert-no-demo-artefacts pass
Mirage gone ✅ zero miragejs/pretender/mocks/ matches in the production bundle
Demo build intact ✅ still ships Mirage — now confined to the dynamically-loaded demo-server chunk, out of the demo entry chunk too
Cypress ✅ all 23 specs, 121 tests, 0 failures
Lock integrity ✅ zero version/resolved/integrity changes to pre-existing packages

The first commit was verified separately to produce a byte-identical bundle (all 113 files matching by content-hashed name and size), isolating the pure-relabelling change from the Mirage fix in the second. develop is merged back in fc91312 — the conflict was package.json/package-lock.json only, resolved keeping vue-tippy/yup in dependencies per this PR; the lockfile was re-resolved with npm install against the merged manifest. Merged back again in 0d5e5f9 after #1042 (Cypress 15.21.0) landed: the same two files conflicted; package.json keeps this PR's stanza layout with develop's versions and scripts on top (cypress 15.21.0, cypress-localstorage-commands ^2.3.0, test:types, the @cypress/* component-testing packages gone), and the lockfile was again re-resolved from develop's copy against the merged manifest — zero version/resolved/integrity changes to pre-existing packages, extract-zip still absent.

Acceptance Criteria

Imported from issue #1041

  1. The sixteen mis-scoped packages above move from devDependencies to dependencies, and husky moves the other way.
  2. npm ci && npm run build:deploy still produces a working bundle, and the flip-ui unit + Cypress suites pass.
  3. A short note in flip-ui/README.md stating the rule — if a non-test file under src/ imports it, statically or via dynamic import(), it is a dependency — so the drift does not recur.

Dependabot derives an alert's "Development" scope label from whether a
package is reachable only through devDependencies. Sixteen packages that
`src/` imports — and that vite therefore bundles into the production
`dist/` — were declared as devDependencies, so an alert on any of them
would arrive labelled "Development" and read as not-shipped, when it is
in the bundle deploy-ui syncs to S3 and CloudFront serves. `husky`, a
git-hook tool, had drifted the other way.

Move all sixteen into dependencies and husky into devDependencies.
Version specifiers are unchanged; the regenerated lockfile carries no
version/resolved/integrity change, only the `dev: true` flags that follow
from the stanza move.

The change is inert at build and install time: vite tree-shakes from the
`src/` entry points regardless of stanza, and no install path in the repo
uses --omit=dev. Verified by building `dist/` from develop's manifest and
from this one — all 113 files match by content-hashed name and byte size.

#1041 originally listed eight packages. A re-sweep found sixteen; the
issue body has been corrected. The eight it missed are @headlessui/vue,
@popperjs/core, codemirror-editor-vue3, date-fns, mime, mitt, underscore
and vuejs-smart-table (the last imported by src/main.ts itself).

highlight.js is deliberately left in dependencies. It has no static
import, so a `from "..."` grep reads it as dev-only, but it is loaded via
`import("highlight.js/lib/core")` in src/utils/highlightJson.ts and does
ship — hence the README note documents matching dynamic import() too, so
the next audit does not "fix" it in the wrong direction.

Signed-off-by: at24_bioeng625-pc <alexandre.triay_bagur@kcl.ac.uk>
@atriaybagur atriaybagur added dependencies Pull requests that update a dependency file ui javaScript labels Aug 26, 2026
@github-actions github-actions Bot changed the title fix(flip-ui): scope shipped packages as dependencies so Dependabot's label is trustworthy (#1041) [Task]: flip-ui devDependencies mis-scoping makes Dependabot's Development label untrustworthy Aug 26, 2026
@github-actions

Copy link
Copy Markdown

✅ Acceptance criteria have been automatically imported from the linked issue(s) and added to the PR description.

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…1041)

Review of the first commit found that its own stated rule — "if a non-test
file under src/ imports it, it is a dependency" — was under-inclusive, and
that the tree violated it. src/main.ts statically imported ../mocks/server,
which is outside src/ and therefore outside the sweep.

Three things follow, and all three are fixed here.

1. Mirage was shipping in the production bundle. The VITE_LOCAL branch that
   calls makeServer() folds correctly, but miragejs patches Error.prototype
   at module scope, so rolldown kept the module and emitted ~230 of them —
   Mirage, Pretender, route-recognizer, inflected and all of lodash — into
   the entry chunk of every build. This is the exact FLIP#794 mechanism that
   src/main.ts already documents for mocks/demo-server; that one was fixed
   with a dynamic import, this one never was. Making it dynamic cuts the
   entry chunk from 331 KB to 182 KB and total shipped JS by 128 KB.

2. Four more packages were mis-scoped. miragejs and uuid ship in the public
   /ark_demo bundle via mocks/demo-server, so they are dependencies whatever
   happens to the main bundle. codemirror and tippy.js are imported directly
   by src/ but were declared in neither stanza — they resolved only through
   npm hoisting a parent's tree, so a dedupe or a parent bump would have
   broken the build, and Dependabot could not scope-label them at all.

3. Prose was never going to hold this line. eslint.config.mjs now runs
   import-x/no-extraneous-dependencies over src/ (minus tests) and mocks/,
   so a devDependency imported from production source fails `npm run lint`
   in CI at the moment it is written. mocks/ is deliberately not exempt —
   it ships in the demo bundle. Verified adversarially: demoting axios back
   to devDependencies reddens the lint.

Bringing mocks/ under the linter surfaced 19 pre-existing errors there
(it was never in the lint glob). Sixteen were formatting, auto-fixed. The
rest were one stale eslint-disable naming @typescript-eslint/ban-types,
a rule removed in typescript-eslint v8 and split into no-empty-object-type
— so it had been suppressing nothing; it now names the rule that exists.

@popperjs/core stays in dependencies even though tree-shaking currently
drops it, because its only consumers (AiSelect/AiChipSelect) are unused.
Scoping keys off the import graph, not the bundle: what production code
imports is stable, whereas what survives tree-shaking flips whenever an
unrelated component starts or stops being used.

Totals: 21 packages corrected — 18 out of devDependencies, 2 that were
declared nowhere, and husky the other way.

Signed-off-by: at24_bioeng625-pc <alexandre.triay_bagur@kcl.ac.uk>
@atriaybagur atriaybagur changed the title [Task]: flip-ui devDependencies mis-scoping makes Dependabot's Development label untrustworthy fix(flip-ui): correct dependency scoping, stop shipping Mirage, and enforce both in CI (#1041) Aug 26, 2026
@github-actions

Copy link
Copy Markdown

✅ Acceptance criteria have been automatically imported from the linked issue(s) and added to the PR description.

@atriaybagur

Copy link
Copy Markdown
Member Author

Addressed in 5a4cb89 — the Mirage removal is now enforced on both sides: a flip-ui/no-static-mirage no-restricted-imports block in the merge-gating lint (static imports of miragejs/pretender/either mock server rejected; dynamic import() untouched), plus Mirage/Pretender library-code sentinels in assert-no-demo-artefacts as the artefact-level backstop for a branch that stops folding. Details in the inline thread. The branch also had mergeable: CONFLICTING (which silently runs zero CI), so develop is merged back in fc91312 — the conflict was package.json/package-lock.json, resolved keeping vue-tippy/yup in dependencies per this PR.

Comment thread flip-ui/package.json
"vue-router": "^4.0.11"
"vue-router": "^4.0.11",
"vue-tippy": "^6.3.1",
"vuejs-smart-table": "^1.0.0-beta.5",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vuejs-smart-table is added here as a new production dependency, but I can't find any import, plugin registration (app.use(...)), or template reference to it anywhere in flip-ui/src or flip-ui/mocks on this branch. The lockfile also gains a brand-new node_modules/vuejs-smart-table node (with its own new vue-demi transitive) rather than a version bump of something pre-existing -- so this isn't just a stanza move like the other packages here. Since this PR's whole premise is "scope keys off the import graph," an entry with no import at all seems to contradict that on its own terms.

Comment thread flip-ui/README.md
until it matters. What it changes is the **scope label on a security alert**: a package that ships to users
but sits in `devDependencies` produces an alert labelled "Development", which reads as *not in the production
bundle* and invites a wrongly-dismissed alert on code CloudFront is serving. FLIP#1041 corrected 21
packages: 18 moved out of `devDependencies`, two that were declared nowhere at all (`codemirror`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This states "18 moved out of devDependencies, two that were declared nowhere at all (codemirror, tippy.js)". Based on the package.json/lockfile diff, vuejs-smart-table was also declared in neither stanza before this PR (no prior dependencies/devDependencies entry, no prior lockfile node) -- so it looks like the "declared nowhere" bucket should be 3, not 2, and the "moved from devDependencies" count should be adjusted down accordingly.

@garciadias garciadias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mechanics of the fix (stanza corrections, the Mirage dynamic-import fix, the two new ESLint guards, and the artefact-sentinel tests) are solid and CI is fully green, but vuejs-smart-table is added as a new production dependency with no import anywhere in the codebase -- directly contradicting both the PR's stated "import graph" methodology and an explicit claim in its first commit message that it's imported by src/main.ts (it is not). Since accurate dependency attribution is this PR's entire reason for existing, please confirm whether this package is actually needed yet, and correct the README's package count accordingly.

@garciadias garciadias assigned atriaybagur and unassigned garciadias Aug 28, 2026
Two conflicts, both in flip-ui's manifest and lockfile, where #1042 (Cypress
14.5.2 -> 15.21.0) landed on develop against this branch's dependency
re-scoping.

- package.json: kept this branch's stanza layout (the 21 re-scoped packages,
  @popperjs/core gone via #1065, eslint-plugin-import-x, the lint glob covering
  mocks/) and took develop's versions and scripts on top: cypress pinned
  15.21.0, cypress-localstorage-commands ^2.3.0, the test:types script, the
  cypress/included:15.21.0 image, and the removal of @cypress/vite-dev-server
  and @cypress/vue.
- package-lock.json: not merged by hand. Started from develop's lockfile and
  re-resolved it against the merged manifest with `npm install
  --package-lock-only`, so the only differences from develop are this branch's
  own: the root stanza move, the dev-flag flips, and the 9 new entries of the
  import-x plugin tree. No pre-existing package changed version, resolved or
  integrity; extract-zip stays gone.

Verified on the merged tree: npm ci clean, lint 0 errors, test:types clean,
1307 unit tests passing, build:deploy's assert-no-demo-artefacts guard green.

Signed-off-by: at24_bioeng625-pc <alexandre.triay_bagur@kcl.ac.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javaScript ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task]: flip-ui devDependencies mis-scoping makes Dependabot's Development label untrustworthy

3 participants