Skip to content

feat(studio): native Tauri app — Phase 1 (shell + Rust pure-state session)#13

Merged
maxlibin merged 34 commits into
mainfrom
feat/studio-native
Jul 5, 2026
Merged

feat(studio): native Tauri app — Phase 1 (shell + Rust pure-state session)#13
maxlibin merged 34 commits into
mainfrom
feat/studio-native

Conversation

@maxlibin

@maxlibin maxlibin commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Phase 1 of rebuilding Studio as a native app (umbrella spec: docs/superpowers/specs/2026-07-04-studio-native-design.md).

What

  • Tauri v2 app in apps/studio/src-tauri hosting the existing SPA — a real macOS Studio.app, 8.8MB (no Node/Electron runtime).
  • Rust session implementing the 9 pure-state commands (state/catalog/set_meta/set_targets/set_persona/set_readme/set_avatar/remove_avatar/reset).
  • Golden cross-impl fixtures: scripts/emit-fixtures.ts emits fixtures/*.json from the real TS StudioSession; a Vitest drift guard pins the TS side and cargo test asserts the Rust session JSON-equals the same fixtures (including empty-string clear semantics, pinned by state-cleared.json).
  • api shim: web/src/api.ts routes the 9 methods to invoke() under Tauri; all 34 other methods reject with a friendly "coming in a later phase" message; browser fetch paths byte-identical (legacy server untouched until Phase 4).
  • CI: new studio-rust macOS job runs cargo test + cargo clippy -D warnings, so TS↔Rust divergence fails the build.

Gates

6/6 cargo · 38/38 vitest · typecheck · clippy — all green. Human smoke test: launch via pnpm --filter @uniqent/studio native:dev.

Out of scope (later phases)

Rust core engine (pack/sign/digest → real export) — Phase 2; builder features — Phase 3; adapters/install, signing/notarization, deleting the Node server — Phase 4.

🤖 Generated with Claude Code

maxlibin and others added 30 commits July 4, 2026 20:33
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ied)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atures

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add apps/studio/README.md with Tauri native dev section
- Fix two clippy lints in session.rs (new_without_default, manual_map)
- Add **/target/** to .prettierignore to prevent prettier scanning Cargo build artifacts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eared fixture

- set_persona("") now keeps the key present as "" with identity: true (mirrors TS Brain.setPersona verbatim store)
- set_readme("  ") now drops the key (mirrors TS trim-empty clears)
- emit-fixtures.ts emits state-cleared.json: canonical mutations → setPersona('')+setReadme('  ')
- Extended TS fixtures.test.ts (38 tests) and Rust fixtures_test.rs (6 tests) with cleared-state case
- Added Phase 1 avatar scope comment in session.rs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add studio-rust job: macos-latest, dtolnay/rust-toolchain@stable,
Swatinem/rust-cache@v2, cargo test + cargo clippy -D warnings.
Sources cargo env in each step for hermetic shell init.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements Task 1 of Phase 2 native Studio app:
- Rust core module (core/bundle.rs, core/digest.rs) with canonical_digest
  matching the TS implementation byte-for-byte (sha2 = "0.10")
- Cross-impl fixture suite: emit script generates deterministic .uniqent
  bundles, extracted files, expected-digest.txt, and a throwaway keypair;
  vitest drift-guard (3 tests) and Rust integration tests (3 tests) both
  assert against the same committed fixtures

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e convention

The emit script wrote uniqent.json without a trailing newline; Prettier then
added one, causing the on-disk files/ tree to diverge from the in-memory bundle
used to compute expected-digest.txt. Rust core_test read from disk and got a
different digest, breaking 2/3 tests.

Fix: add '\n' to the JSON.stringify result in the emit script so the in-memory
bundle, the written files/, and expected-digest.txt all agree byte-for-byte.
Regenerated fixture.uniqent and expected-digest.txt accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After fixing the uniqent.json trailing-newline issue, fixture-signed.uniqent
must be regenerated so it packs the same 353-byte uniqent.json that
expected-digest.txt and the Rust tests now expect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port packages/core/src/secret-scan.ts to Rust (secret_scan.rs):
all 5 PREFIX_PATTERNS (openai/github-pat/slack/aws-access-key/private-key),
high-entropy detector (Shannon >= 4.0 + longest unbroken run >= 20),
PLACEHOLDER_RE, BINARY_EXT skip, ALLOWLISTED_KEYS (pubkey/publicKey),
signature.json skip, JSON/JSONL AST walk with malformed-JSON fallback.

pack_checked() gates on scan before archiving (validation deferred to Phase 3
— diverges from TS pack which also calls assertValid; documented in archive.rs).
sign() gains scan gate at top, matching TS sign() ordering.

19 cargo tests pass (was 8); clippy -D warnings clean; 41 vitest unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Rust JSON walk was capturing only the first hit per JSON object/line
via Option<_>; now collects into a Vec so every string node's finding is
recorded, matching TS semantics where walkJson calls record(detect(s)) for
every string encountered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract buildFixtureBundle() from emit-core-fixtures.ts so tests can call
the live TS core directly; add three drift-guard tests checking digest,
pack/unpack, and sign/verify semantics against committed fixtures.
Guard script body with isMain check so importing the module never writes files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the hard-coded validation.ok=true stub with real regex-based
checks: Slug pattern (^[a-z0-9][a-z0-9-]*$) for name, full semver 2.0.0
pattern for version. Errors carry path/code/manifest structure matching
the TS spec. Add validation_test.rs with 9 cargo tests covering invalid
name, invalid version, both-invalid, and the happy-path default session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mory/profile)

Adds applyContentScript() export to emit-fixtures.ts with the canonical
8-step content mutation script (MCP/skill/channel/task/memory/profile),
emits fixtures/state-content.json, and adds a Vitest drift guard.
Prettier-reformats existing fixture JSON files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Extend emit-fixtures.ts to emit catalog-data.json (mcp/skills/channels from @uniqent/builder)
- Add drift test in fixtures.test.ts to detect catalog-data.json divergence
- Add summarizer entry to SKILL_CATALOG in packages/builder/src/catalog/skills.ts
- Replace hardcoded mcp_catalog_entry/channel_catalog_entry match arms in session.rs
  with OnceLock<Value> lazy-parsed from catalog-data.json via include_str!
- Implement Session::add_skill_catalog(name) mirroring TS Brain.addSkillFromCatalog
- Add Rust unit tests: add_skill_catalog_known_name_adds_skill, add_skill_catalog_unknown_name_errors
- Strengthen readd_mcp_deduplicates to also assert sorted position in state

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ected data

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port TS normalizeMcpConfig (176 lines) to Rust core with full fixture
pipeline: emit-port-fixtures.ts → normalize-cases.json (12 cases),
drift-guard in port-fixtures.test.ts, Rust tests in ports_test.rs.
Session wired: paste_mcp_preview / add_pasted_mcp / import_mcp_servers.
cargo test + clippy -D warnings all green; pnpm test 48/48 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Extend emit-port-fixtures.ts → fixtures/ports/memory-cases.json (11 cases:
  headings+bullets, kind prefixes, [[entity]] links, #tags, blank, mixed,
  dedup, numbered list, alias wikilinks, callout prefix)
- Extend test/port-fixtures.test.ts with memory-cases drift guard
- Add src/ports/memory.rs: parse_memory_markdown + memory_graph (faithful
  port of packages/builder/src/memory/parse.ts, all parse branches: entities,
  tags, kinds, bullet/numbered stripping, heading skip, label truncation)
- Add session: import_memory (items/markdown/text per TS /api/memory/import),
  preview_memory (items + graph, no mutation), memory_graph (from facts)
- Extend tests/ports_test.rs: fixture-mapped tests + 7 session unit tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Register 18 Tauri commands (mcp/skill/channel/task/memory/profile families)
and swap api.ts from soon() to invoke() for all Phase 3a content methods.
Hubs, publish, vault, and install remain stubbed for Phase 3b/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
maxlibin and others added 4 commits July 5, 2026 19:22
…n tree stays clean)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maxlibin maxlibin merged commit a6e0039 into main Jul 5, 2026
3 checks passed
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