Skip to content

ci: cross-platform matrix, full build verify, fmt+lint enforcement#208

Merged
stultus merged 6 commits intomainfrom
ci/cross-platform-and-build-verify
May 3, 2026
Merged

ci: cross-platform matrix, full build verify, fmt+lint enforcement#208
stultus merged 6 commits intomainfrom
ci/cross-platform-and-build-verify

Conversation

@stultus
Copy link
Copy Markdown
Owner

@stultus stultus commented May 3, 2026

Summary

Three changes to .github/workflows/ci.yml:

  1. Cross-platform Rust matrix. Run cargo fmt --check, clippy, and cargo test --lib on ubuntu-22.04, macos-latest, and windows-latest instead of Linux only. Catches platform-specific Rust breakage at PR time instead of at tag time.
  2. End-to-end build verification. New build job runs cargo tauri build --debug on Linux after rust and frontend pass — verifies the full frontend → cargo → tauri-cli pipeline actually produces a bundle. Static checks alone don't guarantee the app builds.
  3. fmt + lint gates. Added cargo fmt --check (Rust side) and npm run lint (= prettier --check . && eslint ., per package.json) to the frontend job.

Also added: workflow-level permissions: contents: read (least-privilege) and timeout-minutes on every job (avoid runaway runs eating action minutes).

Heads-up

If cargo fmt or npm run lint finds existing drift, this PR will fail its own CI on those checks. Fix is to run cargo fmt --all (in src-tauri/) and npm run format locally, commit the fixups onto this branch, and push.

Why

Repo hygiene: existing CI was Linux-only and didn't gate on formatter / lint / full-build. Cross-platform breakage was being caught at release time instead of PR time.

stultus added 3 commits May 3, 2026 12:01
First pass before the new fmt/lint CI gates land. cargo fmt --all on
src-tauri reformatted 11 Rust files (mostly long argument lists and
indentation). prettier --write . reformatted ~60 frontend/docs files
(Svelte, TypeScript, JSON, Markdown). One Svelte file (HelpModal) was
unstable in a single prettier pass and needed a second --write.

eslint debt is not addressed here — 59 errors remain across 8 rule
categories, including svelte/prefer-svelte-reactivity (Svelte 5
migration) and svelte/no-at-html-tags (security review needed). Those
need real code changes and will be tracked separately.
Splitting `npm run lint` into two steps lets the cross-platform CI
land green while we clear 59 existing eslint errors (Svelte 5 runes
migration, unused vars, missing each-block keys, {@html} review).
Flip continue-on-error to false once the debt is paid.
stultus added 2 commits May 3, 2026 12:15
Resolves conflict in .claude/PROGRESS.md by taking main's v0.10.0
docs content as the source of truth, then re-running prettier on the
three docs files to keep them formatter-clean.
The previous merge resolution ran prettier once on this file, but
prettier needs two passes to reach a fixed point on it (likely a
plugin-svelte / markdown-table interaction). Without the second pass,
prettier --check fails CI even though prettier --write was just run.
Last CI run hung for 39 minutes inside Tauri's rpm bundler before
the 45-minute timeout killed it. Cargo compile took only 3m17s and
the .deb bundle finished in 13s; the rpm step never made progress.

Pass --bundles deb,appimage to match the Linux targets we actually
ship in release.yml (no rpm in the release matrix). With rpm out of
the way, the job should finish well under the new 25-minute cap.
@stultus stultus merged commit db5f062 into main May 3, 2026
5 checks passed
@stultus stultus deleted the ci/cross-platform-and-build-verify branch May 3, 2026 08:52
stultus added a commit that referenced this pull request May 3, 2026
Cleaned the eslint debt that has been hanging behind
`continue-on-error: true` since the CI matrix landed (#208), so
ESLint can stand alongside Prettier as a blocking gate.

By rule:

- **svelte/no-unused-svelte-ignore (×11)** — dropped now-dead
  `<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->`
  comments above modal backdrops + dropdowns. Svelte's a11y rules
  recognise `role="dialog"` context now and don't fire on these.
  Files: AboutModal, EpisodeCardsView, ExportModal, HelpModal,
  ImportWizardModal, MetadataModal, NewProjectDialog,
  PasteScriptDialog, SettingsModal (×2), StatisticsModal.
  Also fixed EpisodeCardsView where the actually-needed suppression
  was `_noninteractive_`, not `_static_`.
- **svelte/require-each-key (×10)** — added stable keys to every
  `{#each}` block. Picked natural keys where one existed
  (cell.date ISO, char.name, loc.name, row.sceneNumber, line.label,
  tab.id, warning text), index where the list is positional and
  never reorders (peek headings, weekday names).
- **svelte/prefer-svelte-reactivity (×12)** — split into three
  treatments based on use-site:
  - DatePicker mutated-then-assigned `Date` objects → refactored to
    immutable `new Date(y, m, d)` construction (cleaner anyway).
  - Editor's `collapsedSlots` was a mutable `Set` rebuilt on every
    toggle → replaced with `SvelteSet` from `svelte/reactivity`.
    `.add`/`.delete` now reactively triggers updates without the
    new-Set dance; one downstream call site changed from
    reassignment to `.clear()`.
  - StatisticsModal's seven Maps + SceneCardsView's one Map are
    transient compute scratchpads inside `$derived.by` callbacks
    that never escape — converted SceneCardsView's to a
    `Record<string, number>`; for the StatisticsModal block,
    added a single `eslint-disable svelte/prefer-svelte-reactivity`
    region wrapping the compute function with a rationale.
- **typescript-eslint/no-unused-vars (×9)** — dropped genuinely
  unused vars (`isMalayalam` in Editor, `currentMode` in
  StoryModeView, `StatusBar` import + `startPos` local in
  SceneCardsView, `isPointerOverBubble` + its handlers + bindings
  in FormatBubble). For intentionally-unread reactive-dep names,
  configured `varsIgnorePattern: '^_'` / `argsIgnorePattern: '^_'`
  in eslint.config.js so leading-underscore signals "read for
  subscription". Adjusted `findReplace.ts` Plugin.state.init to
  drop both unused params.
- **typescript-eslint/no-unused-expressions (×5)** — Svelte 5's
  bare-property-read pattern for `$effect` / `$derived.by`
  dependency declaration tripped this. Reassigned each to a
  `_-prefixed` const which the leading-underscore convention
  ignores; comment explains "read for subscription" intent.
- **svelte/no-dom-manipulating (×2)** — SceneCardsView's drag
  ghost overlay genuinely needs imperative `replaceChildren()`.
  Per-line disable with rationale (the ghost owns its own subtree;
  Svelte runtime doesn't render into it).
- **svelte/no-at-html-tags (×2)** — both sites render
  hand-authored literal HTML (`'Title <em>your</em> film'`) with
  no user-input flow. Per-line disable with the safety rationale
  inline.
- **no-control-regex (×1)** — ExportModal's filename sanitiser
  intentionally strips `\x00–\x1f`. Per-line disable.

CI workflow updated: ESLint step is now blocking, with a comment
pointing back at this commit for the rationale.

All gates green: 116 cargo tests pass, clippy clean, svelte-check
0/0 across 319 files, prettier clean, eslint clean.
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