Skip to content

feat(app): add the Tauri shell for Windows and Linux (phase 15) - #19

Merged
hami9 merged 3 commits into
mainfrom
feat/phase-15-app-shell
Aug 25, 2026
Merged

feat(app): add the Tauri shell for Windows and Linux (phase 15)#19
hami9 merged 3 commits into
mainfrom
feat/phase-15-app-shell

Conversation

@hami9

@hami9 hami9 commented Aug 25, 2026

Copy link
Copy Markdown
Owner

What and why

Phase 15, and the first Rust in the repository. A Tauri v2 app that opens, renders a real CanonicalProduct[], and deliberately does not fetch anything — that is phase 16.

Rust is thin, and the line is where §15 draws it. One command reporting the host, a setup hook, the WebView. Everything else is TypeScript. A rule written in Rust cannot be shared with the extension or the companion, so it gets written twice and the copies drift — and the rules most tempting to handle natively are exactly the ones where a second copy is dangerous.

The front end is vanilla TypeScript, same idiom as the popup. Not a framework: the app's reactive surface is a table, a settings pane and a confirmation step, and one language across three surfaces means whoever has read popup.ts can read this. If that stops being true it is a reason to revisit — a better one than adopting a framework because the package was new.

currency.ts is the one safety-critical file here, and it is separate so it can be reasoned about alone. §7.8: reading toman as rial multiplies every price by ten and nothing downstream catches it. Two rules live in the module rather than in the view:

  • No default. undefined means unanswered, and unanswered blocks export. A pre-selected radio button is a guess wearing the costume of a decision.
  • The question is only asked when it is real. If every price already states its unit there is nothing to confirm, and a prompt that fires when there is nothing to decide is one people learn to dismiss — which is how a safety step stops working.

Each choice shows what the first price becomes under that reading (460,000 against 46,000), because the ten-times difference should be visible rather than arithmetic the user does in their head.

§18's design system: the popup's warm palette at a size that is not 360px, dark and light with the override winning in both directions, Persian and English with real RTL through logical properties rather than left/right. Persian digits are a display transform only — the data stays ASCII all the way to the CSV.

The question phase 15 asked directly

Rust joins CI as its own job, not as part of npm run check.

npm run check is what somebody runs on every save. Putting a ten-minute cold cargo build — plus a hard dependency on rustup and three system libraries — in front of a typo fix in an exporter is the wrong trade when most changes to this repository never touch packages/app. A Rust failure still blocks a merge, because the gate is the workflow rather than any one npm script. There is a cargo fmt/clippy -D warnings/cargo test job on this PR and it has to pass.

Also here

Two tooling fixes of the same kind as the earlier .claude/ one: ESLint walks the directory tree rather than git, so it was linting Cargo's target/ output and Tauri's generated schemas. Both ignored. target/ and gen/ are gitignored; Cargo.lock is not, because this is a binary and the lockfile is what makes its build reproducible.

The second commit records a sequencing decision taken after seeing the app run: Windows and Linux get finished before Android starts, so the working order becomes 16, 17, 20, then 18. Phase numbers do not move — they are cited from commits, prompt files and phases.json, and renumbering to express an ordering change would invalidate all of that to say something a sentence says better. What keeps it a deferral rather than a decision to un-make later is already in this PR: the 44px touch target, the single phone breakpoint, logical properties throughout.

How it was verified

npm run check passes — 831 tests, unchanged, since this phase adds a surface rather than engine behaviour.

On Linux: cargo build succeeds, the app opens, and the shell renders the fixture. cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test are all clean. Reviewed running by the maintainer.

Windows is not verified, which is why scripts/release/phases.json still says next for phase 15 rather than done — its own "Done when" asks for both platforms. There is a Windows machine available and that is the remaining box; a native build there is the check, not a cross-compile from Linux (see below).

Two things that are absent on purpose and would be bugs if present: the app makes no network request of any kind, and Export does not write a file. Both are phase 16.

Notes for phase 16, already written into phase-16.md

  • HTTP needs no new abstraction. core already takes an injected HttpClient; the app needs one implementation over invoke, and Layer A, the crawler and politeness then work unchanged because they never knew what was underneath.
  • Politeness must not be reimplemented in Rust. §10's pacing lives in core. A native shell removes the browser's rate limits, which makes §10 more important rather than less.
  • Do not cross-compile. Tauri links against the platform's own WebView, so cross-compiling means cross-compiling those bindings and the bundler. Phase 20 should make the CI job a matrix over ubuntu-latest and windows-latest — a change to the runs-on line and nothing else. Windows will also need an .ico, which src-tauri/icons/ does not have yet.

Checklist

  • The commits follow Conventional Commits — they decide the next version number
  • npm run check passes (format, lint, typecheck, tests)
  • New behaviour has tests
  • No currency unit is inferred, and no field is invented to fill a blank

🤖 Generated with Claude Code

hami9 and others added 3 commits August 25, 2026 12:44
Phase 15, and the first Rust in the repository. A Tauri v2 app that
opens, renders a real `CanonicalProduct[]`, and does not fetch anything
— that is phase 16.

**Rust is thin and the line is drawn where §15 draws it**: one command
reporting the host, a setup hook, and the WebView. Everything else is
TypeScript. A rule written in Rust cannot be shared with the extension
or the companion, so it gets written twice and the copies drift — and
the rules most tempting to "just handle natively" are exactly the ones
where a second copy is dangerous.

**The front end is vanilla TypeScript, same idiom as the popup.** Not a
framework, because the app's reactive surface is a table, a settings
pane and a confirmation step, and one language across three surfaces
means whoever has read `popup.ts` can read this. If that stops being
true it is a reason to revisit — a better one than adopting a framework
because the package was new.

**`currency.ts` is the one safety-critical file here** and is separate
so it can be reasoned about on its own. §7.8: reading toman as rial
multiplies every price by ten and nothing downstream catches it. Two
rules are enforced in the module rather than left to the view — there is
no default, because `undefined` means unanswered and unanswered blocks
export, and the question is only asked when at least one price really is
IRR with no unit stated, since a prompt that fires when there is nothing
to decide is one people learn to dismiss. Each choice shows what the
first price *becomes* under that reading, because the ten-times
difference should be visible rather than arithmetic.

**§18's design system**: the popup's warm palette at a size that is not
360px, dark and light with the override winning in both directions,
Persian and English with real RTL through logical properties rather than
`left`/`right`. Persian digits are a display transform only — the data
stays ASCII all the way to the CSV. The 44px touch target and the
single phone breakpoint are in now rather than retrofitted later.

**Rust joins CI as its own job, not as part of `npm run check`.** Phase
15 asked this directly. `npm run check` is what somebody runs on every
save, and putting a ten-minute cold `cargo build` plus a hard dependency
on rustup and three system libraries in front of a typo fix in an
exporter is the wrong trade when most changes never touch this package.
A Rust failure still blocks a merge, because the gate is the workflow
rather than any one script.

Two tooling fixes of the same kind as the earlier `.claude/` one: ESLint
walks the directory tree rather than git, so it was linting Cargo's
`target/` output and Tauri's generated schemas. Both ignored. `target/`
and `gen/` are gitignored; `Cargo.lock` is not, because this is a binary
and the lockfile is what makes its build reproducible.

Verified on Linux: builds, opens, renders, and `cargo fmt`, `clippy
-D warnings` and `cargo test` are clean. Windows is not verified yet, so
phase 15 stays `next`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decided after seeing phase 15 run. Windows and Linux get completed —
16, 17 and 20 — and Android starts after them.

Android brings a second toolchain and an unresolved distribution
question, so starting it now buys two half-finished platforms instead of
one finished one. The stronger reason is that the desktop UI is about to
be designed directly rather than scaffolded, and designing once against a
shipped app then porting is a different job from designing for two form
factors with neither settled.

The phase numbers do not move. They are cited from commit messages,
prompt files and phases.json, and renumbering to express an ordering
change would invalidate all of that to say something a sentence says
better. Only the order they are worked in changes.

What keeps this a deferral rather than a decision to un-make later is
already in the code: phase 15 put in the 44px touch target, the single
phone breakpoint and logical properties throughout. The constraint that
goes with the deferral is that no desktop-only shortcut gets taken in 16,
17 or 20 on the grounds that Android is far away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 15's own "Done when" asked for Windows and Linux. Linux is
verified — it builds, opens and renders, and CI builds the Rust side on
every pull request. Windows is not, and rather than hold the phase open
for a check that phase 20's build matrix performs anyway, the Windows
verification moves there and is written into the roadmap as something
that phase owns rather than something that got lost.

Two concrete things it inherits, both already known: make the CI job a
matrix over ubuntu-latest and windows-latest, which is the `runs-on`
line because the npm scripts are already platform-neutral, and generate
`icon.ico`, without which the MSI bundle will not build.

Leaving phase 15 as `next` was the alternative and it is worse.
docs/prompts/README.md says a finished phase that still says `next` gets
started again by somebody, and that is a real cost for a box that a
later phase is better placed to tick.

Phase 16 becomes next. The README gains the app in its package table and
its test count catches up to 831.

Also records in packages/app/README.md that the visual design is not
settled in this repository and should not be — what is in `styles.css`
is a working neutral. The structure is already built for that: every
colour and gap is a custom property in one block and `main.ts` hard-codes
neither, so a redesign touches one file rather than the views. Two things
a redesign must not drop are named, because they are §18 requirements
rather than taste — the currency step keeps its own weight and never
becomes a skippable checkbox, and directional rules stay logical
properties or the Persian layout quietly stops being correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hami9
hami9 merged commit 6128d99 into main Aug 25, 2026
2 checks passed
@hami9
hami9 deleted the feat/phase-15-app-shell branch August 25, 2026 10:12
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