Files
notescraft/scripts/waves/wave6/ui-shell.md
T
Marcos Paulo 3da60a0179 Wave 6 setup: first UI prompts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 19:56:50 -03:00

5.9 KiB

Crate: crates/ui-egui (notescraft-ui-egui, layer L5) plus a tiny apps/notescraft/src/main.rs. Part 1 of 2 of the first UI: the app SHELL. Another agent builds note_view.rs, palette.rs and backlinks_panel.rs in the same crate at the same time: do NOT create or edit those files and do not touch their modules; you own: src/shell.rs, src/layout.rs, src/tree.rs, src/tabs.rs, src/statusbar.rs, src/style.rs, src/state.rs, src/app.rs, and apps/notescraft/src/main.rs. lib.rs is shared: only add your own pub mod ...; lines (one per line, alphabetical block of your modules) and never remove others' lines.

READ FIRST: SPEC.md sections 8, 8.1, 8.2, 11; docs/ARCHITECTURE.md; crates/ui-egui/src/theme.rs (the Tokens/color/PANEL_RADIUS/PANEL_GAP contract: use it, do not redefine); crates/engine/src (Session, Registry, core commands); ~/.cargo/git/checkouts/craft-libs-*/7fcee66/crates/ui/README.md and src (craft-ui: tabs, layout, docking, icons, buttons: reuse them where they fit, do not reinvent). Reference for style: /Volumes/Storage/Projects/photocraft/crates/ui-egui/src (read-only). Design reference: PaperCraft project NotesCraft screens "Workspace / macOS" (rounded floating panels on the canvas colour: sidebar 260 pt, editor card fluid, right panel 300 pt; 16 pt radius, 8 pt gaps, tone-separated panels, no borders; tab bar 44 pt tall; status bar with branch, words, chars and approx tokens).

IMPORTANT build rules: edit only the files above. Use cargo test -p notescraft-ui-egui and cargo clippy -p notescraft-ui-egui --all-targets -- -D warnings. Do NOT build the notescraft app crate or eframe (a full eframe build needs gigabytes of disk and would break the machine): the app crate is compiled later by the orchestrator. Write apps/notescraft/src/main.rs carefully without compiling it (see item 7). Build in SMALL steps: one file per tool call (<=250 lines), build + clippy after each file, every module declared in lib.rs. Never use unwrap/expect/panic outside tests; no indexing; user strings in one src/strings.rs-style constants block per module (they move to the i18n crate later).

Implement (all pure egui, no eframe, no wgpu):

  1. style.rs: apply(ctx: &egui::Context, tokens: &Tokens) sets Visuals (dark/light from the theme variant), widget colours, selection, hyperlink colour, window/panel fills, rounding from tokens (no borders: strokes Stroke::NONE), spacing (8 pt scale), and text styles on the 8-step scale (12, 14, 16, 20, 24, 32). Fonts: egui defaults for now.
  2. layout.rs: shell(ctx, tokens, |ui_parts| ...): paints the window canvas colour and lays out three rounded cards (left 260, centre fluid, right 300, widths clamped for narrow windows: below 900 pt the right panel hides, below 640 the sidebar hides) with PANEL_GAP gaps and PANEL_RADIUS corners, each filled with its token colour (surface / background / surface). Expose a Panels closure struct { sidebar, editor, right } of FnOnce(&mut egui::Ui) so callers fill them. Pure function of ctx, tokens and the closures.
  3. tree.rs: file-tree widget over a plain TreeModel built from Session::list-style data (from_paths(&[String]) -> TreeModel, nested folders, folders first then notes alphabetically case-insensitive, .md shown without extension, collapse state in a BTreeSet<String>), rows 32 pt tall with 16 pt indentation, hover/selected states from tokens, returns TreeResponse { opened: Option<String>, toggled: Option<String>, context: Option<String> }.
  4. tabs.rs: tab bar state Tabs { open: Vec<String>, active: usize } with open(path), close(i), activate(i), dedupe, cap 32 tabs; rendering with craft-ui tabs if it fits else egui; 44 pt tall, active tab on surface_2; middle/click-close glyph; returns TabsResponse.
  5. statusbar.rs: StatusInfo { branch: Option<String>, words, chars, tokens } and show(ui, tokens, &StatusInfo); count(text) -> StatusInfo (words by Unicode whitespace, chars by char, tokens = chars/4 rounded up).
  6. state.rs + app.rs: UiState (serde: sidebar/right widths, collapsed folders, open tabs, active tab, theme id) with to_json/from_json (corrupt JSON gives defaults, never panics); NotesApp holding Session, UiState, Tokens, Tabs, TreeModel: NotesApp::new(session, theme) , ui(&mut self, ctx: &egui::Context) drawing the shell with sidebar tree + tab bar + a placeholder editor area (a ui.label of the active note's raw text; the other agent's note_view plugs in later via a single function pointer field note_view: Option<fn(&mut egui::Ui, &Tokens, &str)>), and the status bar; clicking a tree row opens a tab and loads text with Session::read_note; errors from the session shown as a dismissible toast text, never panics.
  7. apps/notescraft/src/main.rs: parse one CLI arg (vault path; if missing, use the NOTESCRAFT_VAULT env var, else print usage to stderr and exit code 2), Session::open, theme catppuccin-mocha via notescraft_themes::builtin, then eframe::run_native with a small eframe::App adapter calling NotesApp::ui (title "NotesCraft", initial size 1440x900, min 640x480). Keep it under 80 lines, no panics (return a Result from main via std::process::ExitCode).

Tests (headless: create egui::Context::default() with default_fonts (dev-dependency feature already enabled), call ctx.run(RawInput { screen_rect: Some(...) , ..Default::default() }, |ctx| ...) for 2 frames and assert no panic and that shapes were produced): style apply for both theme variants; layout at 1440x900, 800x600 (right panel hidden) and 500x400 (only the editor); tree model ordering, nesting, collapse; tabs open/dedupe/close/cap; status counts (unicode, empty); state JSON round trip and corrupt JSON defaults; NotesApp frame run over a temp vault with 5 notes, clicking is simulated by calling the app's open_note(path) method; hostile inputs (10 000 paths, 100-level deep folder path, path with control characters).