Skip to content

build(typecheck): check Vue components with vue-tsc against a baseline; fix broken dependency ranges - #2362

Merged
karlitschek merged 7 commits into
masterfrom
build/typecheck-ratchet
Sep 25, 2026
Merged

karlitschek merged 7 commits into
masterfrom
build/typecheck-ratchet

Conversation

@karlitschek

Copy link
Copy Markdown
Member

Author's description to follow. The list below is a factual index of the commits, not the description.

What each commit addresses

  • fix(deps) — package.json carried babel-loader ^10.26.60, eslint ^10.26.60 and jsdom ^30.26.60 since the 0.26.46 bump rewrote their ranges with the app version; eslint@10.26.60 does not exist, and npm ci hangs reconciling them with the lock. Restored to the pre-bump ranges (^10.1.1, ^10.10.0, ^30.0.1), which the lock already pins. npm ci now completes in about 5 s.
  • build(typecheck) — Frontend typecheck excludes all Vue single-file components #2306: npm run typecheck now also runs vue-tsc over every src/**/*.{js,vue} and compares with a committed baseline (tests/js/typecheck-baseline.json, keyed by file, code and message, not line): new errors fail, removed ones pass with a note to run npm run typecheck:baseline (tools/typecheck.mjs, tested in tests/js/typecheck.test.js).
  • fix(types) — t, n, OC, OCA declared on every component (src/types/vue.d.ts): −280 errors.
  • fix(components) — fields components assign in hooks are now declared (13 files; ZoomableImage gesture state and Composer's Tribute stay non-reactive via setup(), with tests): −117 errors.
  • fix(types) — ServerData, Status.view_count and a ModerationReport typedef: −38 errors.
  • chore(release) — 0.26.62 with the rebuilt bundle (0.26.61 is fix: paging, stale-state, direct-message, nid and remote-media fixes (19 issues) #2361).

Baseline: 719 errors in 89 files before, 284 in 81 files after.

Checks run locally

phpunit (6585), vitest (3102), eslint, stylelint, npm run typecheck (tsc clean; vue-tsc 284, none beyond baseline).

AI disclosure

The code, tests and this index were produced with Claude Code (Claude Opus 5.5) and are awaiting the author's review. Signed-off-by is added by the author.

🤖 Generated with Claude Code

Frank Karlitschek and others added 7 commits September 25, 2026 08:51
The 0.26.46 bump replaced the minor and patch of babel-loader, eslint and
jsdom with the app's own, and every bump since has carried them along:
"^10.26.60" for eslint, a version npm has never published. The lock file
still pins what was installed (10.1.1, 10.10.0, 30.0.1), so a plain build
went on working, but npm ci and npm install have to reconcile the two and
hang for as long as they look for a release that does not exist. These are
the ranges from before that bump.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
…inst a baseline

npm run typecheck ran tsc over the stores, services and utilities only:
tsc cannot resolve a .vue import, so the components, the larger half of the
frontend, were checked against nothing. vue-tsc can, but over the whole of
src/ it reports 719 errors in 89 files today, which is more than one change
should fix.

So the check is a ratchet, the way the psalm baseline is. vue-tsc runs over
every .js and .vue file in src/ with the options jsconfig.json already uses
(jsconfig.vue.json), and tools/typecheck.mjs compares what it reports with
tests/js/typecheck-baseline.json. An error the baseline does not list for
that file, or one more of an error than it counts, fails; errors that went
away pass with a note to run npm run typecheck:baseline. The baseline keys
on file, error code and message, not line and column, so an edit elsewhere
in a file does not make old errors look new, and component instance types
in messages are elided because they spell out every field and change with
each one added.

The tsc check is unchanged and still runs first, allowing no errors. CI
runs both through the existing npm run typecheck step. The test runs the
comparison on recorded output and runs vue-tsc on a component that assigns
a misspelt field, to show the mistake is reported and fails the check.

Fixes #2306

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
…ery component

Every entry point sets t, n, OC and OCA on app.config.globalProperties, and
the templates call t() and n() throughout, but nothing told the type checker
those properties exist. vue-tsc therefore reported every translated string
in a template as a missing property: 280 of the 719 errors in the baseline
were that one cause.

Vue's ComponentCustomProperties is the interface globalProperties are meant
to be declared on. The augmentation is its own module file, because in the
script-style globals.d.ts a `declare module 'vue'` would replace the real
module rather than add to it. The baseline drops from 719 to 439 errors.

Refs #2306

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
Thirteen components kept timers, event handlers, observers and gesture
state on `this` by assigning them in mounted() or created() without
declaring them anywhere. It works at run time, but nothing knows the fields
exist: a misspelt name is a silent new field rather than an error, and
vue-tsc reported every use, 117 of the 439 errors in the baseline.

Timers, handler functions, observers and unsubscribe functions go in
data(), which is where the other components already keep theirs. None of
them is rendered, so nothing re-renders when they change, and Vue does not
wrap functions, numbers or DOM observers in a proxy, so each reads back as
the value that was stored.

Two sets are returned from setup() instead, which exposes them on `this`
without making them reactive. ZoomableImage's pointer map, gesture and pinch
change on every pointermove and nothing renders from them; created() kept
them raw on purpose. The composer's Tribute instance is a plain class
instance, which data() would wrap in a deep reactive proxy that tributejs'
attach() and detach() would then be handed. Tests pin both as unwrapped.

The baseline drops from 439 to 322 errors.

Refs #2306

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
… code reads

The typedefs said less than the server sends, so vue-tsc flagged reads
of fields that are really there. ServerData lacked needsAccount, the admin
setup checks, the sections, the sensitive-media policy and choice, and the
handles the account-creation screen offers, all of which
NavigationController puts in the page. Status lacked view_count, which the
server sends on the author's own copy only. And the moderation table's rows
were typed as nothing at all: its reports prop was a bare Array, so every
cell read a property of unknown; they are now ModerationReport, the shape
Report::moderationRow() builds, in both components that pass it along.

Fields absent from some pages (the public one, a reader who already has an
account, anyone who is not an administrator) are optional. The baseline
drops from 322 to 284 errors.

Refs #2306

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
The settings-order test pinned the last three headings to Migration,
Keyboard shortcuts and the account deletion. Authorized apps has since
been added between Migration and the shortcuts, so the test failed on
every run that got far enough to reach it. What the test is about is
the reference list above the one irreversible action, so it now checks
exactly those two.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
@karlitschek
karlitschek force-pushed the build/typecheck-ratchet branch from e6284c7 to 6fef936 Compare September 25, 2026 06:55
@karlitschek
karlitschek merged commit ed756e8 into master Sep 25, 2026
44 of 45 checks passed
@karlitschek
karlitschek deleted the build/typecheck-ratchet branch September 25, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant