feat: full pattern matrix — M1 complete, M2 LLM chat, 30+ pattern files #10

Open
pyr0ball wants to merge 35 commits from feat/patterns-expansion into main
Owner

Summary

This branch completes M1 (log watching + pattern matching + tray notifications), wires M2 (Ollama streaming chat), and builds out the full migration pattern library.

  • M1 complete: journald, kmsg, and inotify app-log watchers; pattern classifier; tray badge + desktop toast; pending event queue with panel drain
  • M2 LLM chat: llm.rs streaming client against Ollama /api/chat; chat Tauri command; ChatPanel.vue streams tokens character-by-character
  • 30 pattern files: full matrix of source→target migrations across all major OS/distro combinations
  • Multi-step onboarding: source OS → source Linux distro (L-to-L) → dual-boot question

Pattern coverage

Source Targets
macOS arch
Windows debian
Linux (generic / experienced distro-hopper) arch
debian family arch, fedora, opensuse
fedora family arch, debian, opensuse
arch family debian, fedora, opensuse
opensuse family arch, debian, fedora
Android arch, debian, fedora, opensuse
iPad / iOS arch, debian, fedora, opensuse
Dual-boot supplement windows, macos

Android and iPad pattern files assume zero terminal experience — every command explained from first principles with App Store analogies.

Linux-to-Linux files are tuned to the reader's prior tooling: a Fedora user's arch patterns explain DNF analogues; an Arch user's debian patterns explain dpkg recovery and unattended-upgrades.

Dual-boot supplements (dualboot-windows.toml, dualboot-macos.toml) are layered on top of the primary pattern file via PatternFile::extend() when migration.dual_boot_with is set. Covers NTFS Fast Startup, clock skew, GRUB overwrite, BitLocker, T2 Secure Boot, APFS mounting.

New config fields

[migration]
source_os = "windows"          # now includes android, ipad
source_distro_family = "debian" # L-to-L routing
dual_boot_with = "windows"     # triggers supplement loading
distro = "linuxmint"
fluency_level = 0

Onboarding flow (3 steps)

  1. Source OS (Windows / macOS / Another Linux / Android / iPad)
  2. Source distro family — Linux-to-Linux only
  3. Dual-boot question — Windows/macOS only; mobile skips

Test plan

  • cargo test on a machine with webkit2gtk — Munnin (Linux Mint) is available
  • Onboarding: walk each source OS path, confirm correct TOML written to ~/.config/robin/config.toml
  • Pattern matching: trigger a known log line and confirm tray badge + toast appear
  • Windows-to-debian path on Munnin: set source_os = "windows" in config, confirm windows-to-debian.toml loads and NTFS/apt patterns fire correctly on a real Mint machine
  • Dual-boot supplement: set dual_boot_with = "windows", confirm dualboot-windows.toml patterns merge in
  • Chat: with Ollama running (ollama serve), send a message and confirm tokens stream into chat panel
  • Mobile path: set source_os = "android", confirm beginner-friendly pattern bodies appear

Closes #1 (M1 foundation), closes #2 (M2 LLM chat). Relates to #3 (pattern expansion), #4 (Linux-to-Linux paths), #7 (onboarding).

## Summary This branch completes M1 (log watching + pattern matching + tray notifications), wires M2 (Ollama streaming chat), and builds out the full migration pattern library. - **M1 complete**: journald, kmsg, and inotify app-log watchers; pattern classifier; tray badge + desktop toast; pending event queue with panel drain - **M2 LLM chat**: `llm.rs` streaming client against Ollama `/api/chat`; `chat` Tauri command; `ChatPanel.vue` streams tokens character-by-character - **30 pattern files**: full matrix of source→target migrations across all major OS/distro combinations - **Multi-step onboarding**: source OS → source Linux distro (L-to-L) → dual-boot question ## Pattern coverage | Source | Targets | |--------|---------| | macOS | arch | | Windows | debian | | Linux (generic / experienced distro-hopper) | arch | | debian family | arch, fedora, opensuse | | fedora family | arch, debian, opensuse | | arch family | debian, fedora, opensuse | | opensuse family | arch, debian, fedora | | Android | arch, debian, fedora, opensuse | | iPad / iOS | arch, debian, fedora, opensuse | | Dual-boot supplement | windows, macos | Android and iPad pattern files assume zero terminal experience — every command explained from first principles with App Store analogies. Linux-to-Linux files are tuned to the reader's prior tooling: a Fedora user's arch patterns explain DNF analogues; an Arch user's debian patterns explain dpkg recovery and unattended-upgrades. Dual-boot supplements (`dualboot-windows.toml`, `dualboot-macos.toml`) are layered on top of the primary pattern file via `PatternFile::extend()` when `migration.dual_boot_with` is set. Covers NTFS Fast Startup, clock skew, GRUB overwrite, BitLocker, T2 Secure Boot, APFS mounting. ## New config fields ```toml [migration] source_os = "windows" # now includes android, ipad source_distro_family = "debian" # L-to-L routing dual_boot_with = "windows" # triggers supplement loading distro = "linuxmint" fluency_level = 0 ``` ## Onboarding flow (3 steps) 1. Source OS (Windows / macOS / Another Linux / Android / iPad) 2. Source distro family — Linux-to-Linux only 3. Dual-boot question — Windows/macOS only; mobile skips ## Test plan - [ ] `cargo test` on a machine with webkit2gtk — Munnin (Linux Mint) is available - [ ] Onboarding: walk each source OS path, confirm correct TOML written to `~/.config/robin/config.toml` - [ ] Pattern matching: trigger a known log line and confirm tray badge + toast appear - [ ] **Windows-to-debian path on Munnin**: set `source_os = "windows"` in config, confirm `windows-to-debian.toml` loads and NTFS/apt patterns fire correctly on a real Mint machine - [ ] Dual-boot supplement: set `dual_boot_with = "windows"`, confirm `dualboot-windows.toml` patterns merge in - [ ] Chat: with Ollama running (`ollama serve`), send a message and confirm tokens stream into chat panel - [ ] Mobile path: set `source_os = "android"`, confirm beginner-friendly pattern bodies appear ## Related issues Closes #1 (M1 foundation), closes #2 (M2 LLM chat). Relates to #3 (pattern expansion), #4 (Linux-to-Linux paths), #7 (onboarding).
pyr0ball added 25 commits 2026-05-19 10:45:37 -07:00
- parse_id now strips both double and single quotes per os-release spec
- distro_family debian arm includes "mint" (legacy Linux Mint ID)
- Add tests: single-quoted ID round-trip, mint family classification
Add EventSource enum and update SystemEvent in watcher.rs (M0 stub
updated to support Task 5 clean deletion). Create patterns.rs with
PatternFile/Pattern/MatchedEvent types, TOML loader, and classify()
matching against source + text. Five unit tests covering journald,
applog, no-match, and source discrimination cases.
- load() now rejects patterns with empty match_text or empty sources list
- EventSource derives Serialize/Deserialize with serde tag for emit() readiness
- AppLog variant changed to struct form (AppLog { app }) for tagged enum compat
- classify() takes &SystemEvent directly (top-level use import, not per-fn)
- #[must_use] on classify()
- 5 new tests: any-source wildcard (journald+kmsg), applog mismatch, empty-field validation
Replace flat watcher.rs with watcher/ module containing mod.rs plus stub
sub-modules for journald, kmsg, and inotify. Upgrades spawn() to accept
log_paths and return mpsc::Receiver<SystemEvent>. Updates lib.rs call site.
- read_to_end + from_utf8_lossy replaces read_to_string so Wine/game logs
  with Latin-1 bytes are handled via U+FFFD replacement instead of silently
  dropping all events from that file
- bytes_read from I/O call used for new_pos (not content.len()) for correct
  byte position accounting
- spawn_blocking handle is now awaited so panics inside the blocking task
  surface to the caller instead of being silently swallowed
- lib.rs: replaces stub setup with full wiring: loads PatternFile from
  config, extracts log_paths, spawns watcher, runs classifier loop in
  async task, dispatches MatchedEvents via notify::dispatch
- lib.rs: config Mutex lock uses unwrap_or_else(|e| e.into_inner()) to
  recover from poison instead of panicking
- patterns.rs: load() now tries three path candidates in order
  (dev-relative, src-tauri-relative, system) before returning bail!
  Validation loop (match_text, sources) retained inside candidate loop
macOS-to-Arch: +9 patterns (pacman lock, dep conflict, DKMS build fail,
PipeWire, WirePlumber, Bluetooth rfkill/profile, GPU hang, XWayland crash,
OOM killer, disk I/O, NetworkManager)

Windows-to-Debian: +11 patterns (dpkg interrupted, PipeWire, PulseAudio,
Bluetooth rfkill/profile, NTFS dirty/force flags, disk I/O, USB reset,
OOM killer, GPU hang, NetworkManager, CUPS unavailable)

All patterns target sources Robin actually watches: journald, kmsg, applog.
match_text strings use invariant substrings from real log output.
Targets an experienced Debian/Fedora user on first Arch install. Body text
is terse and technical — no hand-holding, just Arch-specific gotchas.

- pacman.log as watched applog source (catches terminal pacman/AUR runs)
- pacman/AUR: db lock, dep conflicts, conflicting files, build failures,
  PGP keys, missing makedepends, partial upgrade warning, Chaotic-AUR sig
- DKMS: build failures + CachyOS kernel module signature mismatch
- System: locale not generated (Arch vs Debian difference), systemd-resolved,
  OOM (zram suggestion), disk I/O
- Audio: PipeWire/WirePlumber, Bluetooth rfkill + profile
- GPU: hang (mesa-git/DKMS version mismatch context), XWayland crash
- Gaming: Proton, Lutris Wine runner
- Network: NetworkManager + Realtek firmware callout for CachyOS
Pattern files: 12 cross-family migration pairs covering debian, fedora, arch,
opensuse — each tuned to the user's prior tooling (apt, dnf, pacman, zypper).
Includes the custom linux-to-arch file for experienced distro-hoppers and
the macos-to-arch / windows-to-debian expansions from the prior session.

Code changes:
- patterns::load() accepts source_distro_family: Option<&str> — tries
  specific debian-to-arch.toml before falling back to linux-to-arch.toml
- MigrationConfig adds source_distro_family: Option<String> with serde default
- complete_onboarding() accepts optional source_distro arg and derives family
  via distro_family() for Linux-to-Linux migrations
- llm.rs: Ollama streaming client with Vec<u8> buffer for UTF-8 safety,
  emit errors logged not silenced
- commands::chat: spawns stream task, returns immediately so frontend
  isn't blocked waiting for full LLM response
- lib.rs: registers mod llm and commands::chat in invoke_handler
New SourceOs variants: Android, IpadOs — routed to android-to-* and
ipad-to-* pattern files respectively. Pattern bodies assume zero terminal
experience; every command explained from first principles with App Store /
iOS analogies.

Dual-boot supplement system: PatternFile::extend() + load_supplement()
in patterns.rs; lib.rs loads dualboot-{windows,macos}.toml on top of the
primary pattern file when migration.dual_boot_with is set. Supplement
covers NTFS dirty flag from Fast Startup, clock skew (RTC local vs UTC),
GRUB overwrite by Windows Update, BitLocker, APFS/HFS+ access, T2 Secure
Boot.

complete_onboarding() now accepts dual_boot_with: Option<String> and
normalises it to "windows"/"macos". Onboarding.vue becomes a 3-step flow:
source OS -> (Linux distro if linux) -> (dual-boot if windows/macos).
Mobile users skip the dual-boot step entirely.

10 new pattern files (8 mobile + 2 supplements), config.rs tests updated.
pyr0ball added 1 commit 2026-05-20 08:32:48 -07:00
- Add reqwest 0.12 (json + stream features) — was missing from Cargo.toml
  causing chat_stream to fail to compile
- Add Android/IpadOs arms to SourceOs match in chat command
- Add tauri::Manager import to notify.rs (needed for .state())
- Replace deprecated menu_on_left_click with show_menu_on_left_click
- Remove desktopTemplate from tauri.conf.json (not in tauri-utils 2.9.2 schema)
- Commit Cargo.lock so cross-machine builds pin identical crate versions

All 42 unit tests pass on Linux Mint 22.3 (Muninn).
pyr0ball added 1 commit 2026-05-20 09:59:46 -07:00
- Fix main.rs: app_lib crate name was wrong, should be robin_lib
- Add log::info! on pattern load success/failure — startup was completely
  silent making smoke testing and production diagnosis impossible
- Log pattern count and dual-boot supplement count on load
- Log matched pattern id on every notification dispatch
- These messages appear in ~/.local/share/tech.circuitforge.robin/logs/Robin.log
pyr0ball added 1 commit 2026-05-20 11:06:58 -07:00
manage.sh:
- run: foreground execution with auto DISPLAY + RUST_LOG
- start/stop/restart/status: background daemon lifecycle
- logs: tail Robin log file
- build-debug: Rust-only build (no Node/npm needed)
- desktop-install/remove: system application menu entry
- autostart-enable/disable: XDG autostart entry
- install: build-debug + desktop-install + autostart-enable in one step
- uninstall: reverses install cleanly

patterns.rs:
- Add exe-relative candidates: binary-dir/patterns/ and binary-dir/../../patterns/
  This means the binary works from any working directory without requiring
  the user to cd into ~/robin/ first
pyr0ball added 1 commit 2026-05-20 11:10:27 -07:00
Switched from Builder::run() to Builder::build() + App::run(event_handler)
so ExitRequested can be intercepted. Without this, the hidden chat window
failing to load localhost:1420 triggered a clean exit and Robin disappeared
from the tray silently.
pyr0ball added 1 commit 2026-05-20 11:16:42 -07:00
vite.config.ts:
- Set server.port = 1420 with strictPort = true to match tauri.conf.json devUrl
- Add clearScreen = false to keep Vite output readable alongside Rust logs

manage.sh:
- start: auto-detects debug build + absent dev server, launches Vite via nvm
- stop: also kills Vite dev server if running
- This makes ./manage.sh start a single command that handles everything

lib.rs:
- Switch to Builder::build() + App::run(event_handler) form
- Intercept RunEvent::ExitRequested to prevent process exit when
  chat window closes — keeps Robin alive in tray indefinitely
pyr0ball added 1 commit 2026-05-20 11:18:28 -07:00
Intercept WindowEvent::CloseRequested on the 'chat' window and call
window.hide() + api.prevent_close(). Without this, clicking the X
destroys the window — get_webview_window("chat") then returns None
and subsequent tray clicks do nothing.
pyr0ball added 3 commits 2026-05-24 22:03:28 -07:00
The UI was previously a Vue 3 SPA served via a Vite dev server on :1420.
This had two problems:
  - Building from source required Node, npm, and the Tauri CLI
  - Running required starting a Vite dev server alongside the binary

Replace with a single self-contained dist/index.html using vanilla JS
and the Tauri 2 withGlobalTauri IPC global. No build step, no npm, no
dev server — the binary loads the static file directly.

Changes:
  - dist/index.html: full Robin UI (chat, events, debug tabs) in ~750 LOC
      - Chat tab: streaming LLM responses via robin:chat-token events
      - Events tab: live matched system events with severity badges
      - Debug tab: migration config, Ollama status probe, notif level picker
      - Onboarding form shown on first run (calls complete_onboarding IPC)
      - All user/LLM text via textContent/DOM construction, no innerHTML
      - Markdown renderer (fenced code, inline code) built from DOM nodes
  - tauri.conf.json: add withGlobalTauri: true, remove devUrl and Node hooks
  - tauri.conf.json: update CSP to allow inline scripts (desktop app)
  - manage.sh: remove Vite dev server auto-start and kill logic
  - manage.sh: build/dev now use cargo directly, add bundle command for
    full .deb/.rpm/.AppImage (still requires Tauri CLI)
  - .gitignore: track dist/index.html, only ignore dist/assets/ (Vite output)
Real-world trigger: libQt6PrintSupport.so.6 missing from CheatEngine
portable binary on Linux Mint 22.3.

Windows/macOS migrants double-click a binary, nothing happens, and they
have no idea why — the dynamic linker error goes to journald silently.
Robin catches 'cannot open shared object file: No such file or directory'
in journald and explains the Linux shared library model, pointing to the
right package manager command (apt/pacman/dnf/zypper) per distro family.

Also documents why 'pip install pyqt6' doesn't fix system library errors.

Added to 23 pattern files covering all source OS / target distro family
combinations.
Adds 141 new pattern entries via expansion script:

Universal (all 25 files):
- slow-boot-network-wait: detect NetworkManager-wait-online stalling boot
- slow-boot-device-timeout: detect fstab entries for disconnected devices
- slow-boot-long-running-job: surface slow service with systemd-analyze hint
- ssh-permissions-key: catch unprotected private key file warning
- flatpak-missing-runtime: detect missing Flatpak runtime with update/reinstall advice

Per distro family:
- apparmor-denial: added to windows-to-debian (only missing debian target)
- xwayland-crash: added to all files missing it, with distro-correct install cmd
  (apt/pacman/dnf/zypper per target family)

All 42 Rust unit tests pass.
pyr0ball added 1 commit 2026-05-24 22:08:45 -07:00
- favicon.svg: CF lightning bolt as favicon placeholder until Robin
  bird icon is designed; wired into index.html <link rel=icon>
- icons.svg: leftover social media sprite sheet from old Vue setup,
  added to .gitignore (not referenced by the static UI)
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/patterns-expansion:feat/patterns-expansion
git checkout feat/patterns-expansion
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Circuit-Forge/robin#10
No description provided.