Files
notescraft/specs/large-vaults-and-code/design/dialog-large-folder-warning.md
Marcos Paulo 5e8020aa38
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: Obsidian basics, editor previews, graph rework, query blocks, stacked tabs, pop-out windows
Editor previews (colour swatches, arrows, embeds), the 18 obsidian-basics tasks (find and replace
with regex, back/forward, auto-pair, footnotes, folding, search operators, split view, PDF/audio/
video embeds, spelling, drag and drop, pinned tabs, settings) and round 2: ```query blocks, stacked
tabs, pop-out windows, tasks ticked inside embeds, text around an embed, embeds that follow edits,
block menu shortcut. New crates: notescraft-regex (linear-time engine) and notescraft-syntax.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 11:21:19 -03:00

17 KiB

AI UI Implementation Spec: Dialog / Large folder warning (egui)

1. Mandatory Enforcement Rules

  • MUST NOT use position: absolute (or Tailwind absolute) on in-flow children (15 flow nodes under root)
  • MUST use token --canvas (or tokens::CANVAS) instead of hardcoded hex #11111b on Dialog / Large folder warning (#783)
  • MUST use column auto-layout on Dialog / Large folder warning (#783) with gap 0px and padding [48px, 48px, 48px, 48px]
  • MUST use token --surface (or tokens::SURFACE) instead of hardcoded hex #181825 on Dialog / Large folder (#784)
  • MUST use column auto-layout on Dialog / Large folder (#784) with gap 16px and padding [24px, 24px, 24px, 24px]
  • MUST use row auto-layout on Frame (#785) with gap 12px and padding [0px, 0px, 0px, 0px]
  • MUST use token --text (or tokens::TEXT) instead of hardcoded hex #cdd6f4 on Open this large folder? (#790)
  • MUST include exact text string "Open this large folder?" for Open this large folder? (#790)
  • MUST use token --bg (or tokens::BG) instead of hardcoded hex #1e1e2e on Folder Path (#791)
  • MUST use row auto-layout on Folder Path (#791) with gap 8px and padding [8px, 12px, 8px, 12px]
  • MUST use token --text (or tokens::TEXT) instead of hardcoded hex #cdd6f4 on /Volumes/Storage/Projects/note (#794)
  • MUST include exact text string "/Volumes/Storage/Projects/notescraft" for /Volumes/Storage/Projects/note (#794)
  • MUST use column auto-layout on Stats (#795) with gap 4px and padding [0px, 0px, 0px, 0px]
  • MUST use token --text (or tokens::TEXT) instead of hardcoded hex #cdd6f4 on 12,408 files, 3,102 notes. No (#796)
  • MUST include exact text string "12,408 files, 3,102 notes. No .obsidian or .notescraft folder found, so this may not be a vault." for 12,408 files, 3,102 notes. No (#796)
  • MUST use token --text-muted (or tokens::TEXT_MUTED) instead of hardcoded hex #a6adc8 on Opening adds a .notescraft fol (#797)
  • MUST include exact text string "Opening adds a .notescraft folder here." for Opening adds a .notescraft fol (#797)
  • MUST use row auto-layout on Dialog Actions (#798) with gap 8px and padding [0px, 0px, 0px, 0px]
  • MUST preserve component instance Button / Secondary (#799) referencing Button / Secondary
  • MUST use token --surface-2 (or tokens::SURFACE_2) instead of hardcoded hex #313244 on Button / Secondary (#799)
  • MUST use row auto-layout on Button / Secondary (#799) with gap 8px and padding [12px, 16px, 12px, 16px]
  • MUST use token --text (or tokens::TEXT) instead of hardcoded hex #cdd6f4 on Secondary (#800)
  • MUST include exact text string "Cancel" for Secondary (#800)
  • MUST preserve component instance Button / Primary (#801) referencing Button / Primary
  • MUST use token --accent (or tokens::ACCENT) instead of hardcoded hex #cba6f7 on Button / Primary (#801)
  • MUST use row auto-layout on Button / Primary (#801) with gap 8px and padding [12px, 16px, 12px, 16px]
  • MUST use token --on-accent (or tokens::ON_ACCENT) instead of hardcoded hex #11111b on Primary action (#802)
  • MUST include exact text string "Open anyway" for Primary action (#802)

2. Design Tokens & Variables

  • --accent: #cba6f7
  • --bg: #1e1e2e
  • --border: #45475a
  • --callout-abstract: #94e2d5
  • --callout-bug: #f38ba8
  • --callout-danger: #f38ba8
  • --callout-example: #cba6f7
  • --callout-failure: #f38ba8
  • --callout-info: #74c7ec
  • --callout-note: #89b4fa
  • --callout-question: #f9e2af
  • --callout-quote: #a6adc8
  • --callout-success: #a6e3a1
  • --callout-tip: #94e2d5
  • --callout-todo: #89b4fa
  • --callout-warning: #fab387
  • --canvas: #11111b
  • --danger: #f38ba8
  • --heat-1: #232336
  • --heat-2: #2d2d47
  • --heat-3: #3d3d63
  • --highlight: #4a4a45
  • --link: #89b4fa
  • --link-unresolved: #f38ba8
  • --on-accent: #11111b
  • --success: #a6e3a1
  • --surface: #181825
  • --surface-2: #313244
  • --syn-comment: #8a8fa8
  • --syn-function: #89b4fa
  • --syn-key: #74c7ec
  • --syn-keyword: #cba6f7
  • --syn-number: #fab387
  • --syn-punct: #9399b2
  • --syn-string: #a6e3a1
  • --syn-type: #f9e2af
  • --text: #cdd6f4
  • --text-muted: #a6adc8
  • --warning: #f9e2af
  • --radius-lg: 16px
  • --radius-md: 12px
  • --radius-pill: 999px
  • --radius-sm: 8px
  • --radius-xl: 24px
  • --space-1: 4px
  • --space-12: 48px
  • --space-16: 64px
  • --space-2: 8px
  • --space-3: 12px
  • --space-4: 16px
  • --space-6: 24px
  • --space-8: 32px
  • --target-row: 40px
  • --target-touch: 44px
  • --text-2xl: 32px
  • --text-lg: 20px
  • --text-md: 16px
  • --text-sm: 14px
  • --text-xl: 24px
  • --text-xs: 12px
  • --width-phone: 390px
  • --width-right-card: 420px
  • --width-sidebar: 240px
  • Modes: Dark, Light

3. Structured Layout Hierarchy

  • Dialog / Large folder warning (#783, frame): size=720x364.2 (h:fixed, v:hug, in-flow) | flex=column gap=0px pad=[48,48,48,48] | fill=var(--canvas)
    • Dialog / Large folder (#784, frame): size=520x268.2 (h:fixed, v:hug, in-flow) | flex=column gap=16px pad=[24,24,24,24] | fill=var(--surface)
      • Frame (#785, frame): size=472x24.2 (h:fill, v:hug, in-flow) | flex=row gap=12px pad=[0,0,0,0]
        • Icon / lucide:triangle-alert (#786, frame): size=24x24 (h:fixed, v:fixed, in-flow)
          • Path (#787, vector): size=20.02x18.01 (h:fixed, v:fixed, absolute)
          • Path (#788, vector): size=0x4 (h:fixed, v:fixed, absolute)
          • Path (#789, vector): size=0.01x0 (h:fixed, v:fixed, absolute)
        • Open this large folder? (#790, text): size=222.22x24.2 (h:fixed, v:fixed, in-flow) | fill=var(--text) | text="Open this large folder?"
      • Folder Path (#791, frame): size=472x40 (h:fill, v:fixed, in-flow) | flex=row gap=8px pad=[8,12,8,12] | fill=var(--bg)
        • Icon / lucide:folder (#792, frame): size=16x16 (h:fixed, v:fixed, in-flow)
          • Path (#793, vector): size=13.33x11.33 (h:fixed, v:fixed, absolute)
        • /Volumes/Storage/Projects/note (#794, text): size=251.37x16.94 (h:fixed, v:fixed, in-flow) | fill=var(--text) | text="/Volumes/Storage/Projects/notescraft"
      • Stats (#795, frame): size=472x64 (h:fill, v:hug, in-flow) | flex=column gap=4px pad=[0,0,0,0]
        • 12,408 files, 3,102 notes. No (#796, text): size=472x40 (h:fill, v:fixed, in-flow) | fill=var(--text) | text="12,408 files, 3,102 notes. No .obsidian or .notescraft folder found, so this may not be a vault."
        • Opening adds a .notescraft fol (#797, text): size=472x20 (h:fill, v:fixed, in-flow) | fill=var(--text-muted) | text="Opening adds a .notescraft folder here."
      • Dialog Actions (#798, frame): size=472x44 (h:fill, v:hug, in-flow) | flex=row gap=8px pad=[0,0,0,0]
        • Button / Secondary (#799, frame): size=79.57x44 (h:hug, v:hug, in-flow) | flex=row gap=8px pad=[12,16,12,16] | fill=var(--surface-2)
          • Secondary (#800, text): size=47.57x20 (h:fixed, v:fixed, in-flow) | fill=var(--text) | text="Cancel"
        • Button / Primary (#801, frame): size=125.71x44 (h:hug, v:hug, in-flow) | flex=row gap=8px pad=[12,16,12,16] | fill=var(--accent)
          • Primary action (#802, text): size=93.71x20 (h:fixed, v:fixed, in-flow) | fill=var(--on-accent) | text="Open anyway"

4. Reference Implementation (egui)

pub mod tokens {
    pub const ACCENT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(203, 166, 247, 255);
    pub const BG: egui::Color32 = egui::Color32::from_rgba_unmultiplied(30, 30, 46, 255);
    pub const BORDER: egui::Color32 = egui::Color32::from_rgba_unmultiplied(69, 71, 90, 255);
    pub const CALLOUT_ABSTRACT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(148, 226, 213, 255);
    pub const CALLOUT_BUG: egui::Color32 = egui::Color32::from_rgba_unmultiplied(243, 139, 168, 255);
    pub const CALLOUT_DANGER: egui::Color32 = egui::Color32::from_rgba_unmultiplied(243, 139, 168, 255);
    pub const CALLOUT_EXAMPLE: egui::Color32 = egui::Color32::from_rgba_unmultiplied(203, 166, 247, 255);
    pub const CALLOUT_FAILURE: egui::Color32 = egui::Color32::from_rgba_unmultiplied(243, 139, 168, 255);
    pub const CALLOUT_INFO: egui::Color32 = egui::Color32::from_rgba_unmultiplied(116, 199, 236, 255);
    pub const CALLOUT_NOTE: egui::Color32 = egui::Color32::from_rgba_unmultiplied(137, 180, 250, 255);
    pub const CALLOUT_QUESTION: egui::Color32 = egui::Color32::from_rgba_unmultiplied(249, 226, 175, 255);
    pub const CALLOUT_QUOTE: egui::Color32 = egui::Color32::from_rgba_unmultiplied(166, 173, 200, 255);
    pub const CALLOUT_SUCCESS: egui::Color32 = egui::Color32::from_rgba_unmultiplied(166, 227, 161, 255);
    pub const CALLOUT_TIP: egui::Color32 = egui::Color32::from_rgba_unmultiplied(148, 226, 213, 255);
    pub const CALLOUT_TODO: egui::Color32 = egui::Color32::from_rgba_unmultiplied(137, 180, 250, 255);
    pub const CALLOUT_WARNING: egui::Color32 = egui::Color32::from_rgba_unmultiplied(250, 179, 135, 255);
    pub const CANVAS: egui::Color32 = egui::Color32::from_rgba_unmultiplied(17, 17, 27, 255);
    pub const DANGER: egui::Color32 = egui::Color32::from_rgba_unmultiplied(243, 139, 168, 255);
    pub const HEAT_1: egui::Color32 = egui::Color32::from_rgba_unmultiplied(35, 35, 54, 255);
    pub const HEAT_2: egui::Color32 = egui::Color32::from_rgba_unmultiplied(45, 45, 71, 255);
    pub const HEAT_3: egui::Color32 = egui::Color32::from_rgba_unmultiplied(61, 61, 99, 255);
    pub const HIGHLIGHT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(74, 74, 69, 255);
    pub const LINK: egui::Color32 = egui::Color32::from_rgba_unmultiplied(137, 180, 250, 255);
    pub const LINK_UNRESOLVED: egui::Color32 = egui::Color32::from_rgba_unmultiplied(243, 139, 168, 255);
    pub const ON_ACCENT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(17, 17, 27, 255);
    pub const SUCCESS: egui::Color32 = egui::Color32::from_rgba_unmultiplied(166, 227, 161, 255);
    pub const SURFACE: egui::Color32 = egui::Color32::from_rgba_unmultiplied(24, 24, 37, 255);
    pub const SURFACE_2: egui::Color32 = egui::Color32::from_rgba_unmultiplied(49, 50, 68, 255);
    pub const SYN_COMMENT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(138, 143, 168, 255);
    pub const SYN_FUNCTION: egui::Color32 = egui::Color32::from_rgba_unmultiplied(137, 180, 250, 255);
    pub const SYN_KEY: egui::Color32 = egui::Color32::from_rgba_unmultiplied(116, 199, 236, 255);
    pub const SYN_KEYWORD: egui::Color32 = egui::Color32::from_rgba_unmultiplied(203, 166, 247, 255);
    pub const SYN_NUMBER: egui::Color32 = egui::Color32::from_rgba_unmultiplied(250, 179, 135, 255);
    pub const SYN_PUNCT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(147, 153, 178, 255);
    pub const SYN_STRING: egui::Color32 = egui::Color32::from_rgba_unmultiplied(166, 227, 161, 255);
    pub const SYN_TYPE: egui::Color32 = egui::Color32::from_rgba_unmultiplied(249, 226, 175, 255);
    pub const TEXT: egui::Color32 = egui::Color32::from_rgba_unmultiplied(205, 214, 244, 255);
    pub const TEXT_MUTED: egui::Color32 = egui::Color32::from_rgba_unmultiplied(166, 173, 200, 255);
    pub const WARNING: egui::Color32 = egui::Color32::from_rgba_unmultiplied(249, 226, 175, 255);
    pub const RADIUS_LG: f32 = 16.0;
    pub const RADIUS_MD: f32 = 12.0;
    pub const RADIUS_PILL: f32 = 999.0;
    pub const RADIUS_SM: f32 = 8.0;
    pub const RADIUS_XL: f32 = 24.0;
    pub const SPACE_1: f32 = 4.0;
    pub const SPACE_12: f32 = 48.0;
    pub const SPACE_16: f32 = 64.0;
    pub const SPACE_2: f32 = 8.0;
    pub const SPACE_3: f32 = 12.0;
    pub const SPACE_4: f32 = 16.0;
    pub const SPACE_6: f32 = 24.0;
    pub const SPACE_8: f32 = 32.0;
    pub const TARGET_ROW: f32 = 40.0;
    pub const TARGET_TOUCH: f32 = 44.0;
    pub const TEXT_2XL: f32 = 32.0;
    pub const TEXT_LG: f32 = 20.0;
    pub const TEXT_MD: f32 = 16.0;
    pub const TEXT_SM: f32 = 14.0;
    pub const TEXT_XL: f32 = 24.0;
    pub const TEXT_XS: f32 = 12.0;
    pub const WIDTH_PHONE: f32 = 390.0;
    pub const WIDTH_RIGHT_CARD: f32 = 420.0;
    pub const WIDTH_SIDEBAR: f32 = 240.0;
}

pub fn render(ui: &mut egui::Ui) {
    egui::Frame::new().fill(tokens::CANVAS).inner_margin(egui::Margin { top: 48, right: 48, bottom: 48, left: 48 }).show(ui, |ui| {
        ui.set_min_width(720.0 as f32);
        ui.set_min_height(364.2 as f32);
        ui.spacing_mut().item_spacing = egui::vec2(0.0 as f32, 0.0 as f32);
        ui.vertical(|ui| {
            egui::Frame::new().fill(tokens::SURFACE).corner_radius(16.0 as f32).stroke(egui::Stroke::new(1.0 as f32, egui::Color32::from_rgba_unmultiplied(69, 71, 90, 255))).inner_margin(egui::Margin { top: 24, right: 24, bottom: 24, left: 24 }).show(ui, |ui| {
                ui.set_min_width(520.0 as f32);
                ui.spacing_mut().item_spacing = egui::vec2(16.0 as f32, 16.0 as f32);
                ui.vertical(|ui| {
                    egui::Frame::new().show(ui, |ui| {
                        ui.spacing_mut().item_spacing = egui::vec2(12.0 as f32, 12.0 as f32);
                        ui.horizontal(|ui| {
                            egui::Frame::new().show(ui, |ui| {
                                ui.set_min_width(24.0 as f32);
                                ui.set_min_height(24.0 as f32);
                                let (rect, _) = ui.allocate_exact_size(egui::vec2(20.02 as f32, 18.01 as f32), egui::Sense::hover());
                                ui.painter().rect_filled(rect, 0.0 as f32, egui::Color32::TRANSPARENT);
                                let (rect, _) = ui.allocate_exact_size(egui::vec2(0.0 as f32, 4.0 as f32), egui::Sense::hover());
                                ui.painter().rect_filled(rect, 0.0 as f32, egui::Color32::TRANSPARENT);
                                let (rect, _) = ui.allocate_exact_size(egui::vec2(0.01 as f32, 0.0 as f32), egui::Sense::hover());
                                ui.painter().rect_filled(rect, 0.0 as f32, egui::Color32::TRANSPARENT);
                            });
                            ui.label(egui::RichText::new("Open this large folder?").size(20.0 as f32).color(tokens::TEXT).strong());
                        });
                    });
                    egui::Frame::new().fill(tokens::BG).corner_radius(8.0 as f32).inner_margin(egui::Margin { top: 8, right: 12, bottom: 8, left: 12 }).show(ui, |ui| {
                        ui.set_min_height(40.0 as f32);
                        ui.spacing_mut().item_spacing = egui::vec2(8.0 as f32, 8.0 as f32);
                        ui.horizontal(|ui| {
                            egui::Frame::new().show(ui, |ui| {
                                ui.set_min_width(16.0 as f32);
                                ui.set_min_height(16.0 as f32);
                                let (rect, _) = ui.allocate_exact_size(egui::vec2(13.33 as f32, 11.33 as f32), egui::Sense::hover());
                                ui.painter().rect_filled(rect, 0.0 as f32, egui::Color32::TRANSPARENT);
                            });
                            ui.label(egui::RichText::new("/Volumes/Storage/Projects/notescraft").size(14.0 as f32).color(tokens::TEXT));
                        });
                    });
                    egui::Frame::new().show(ui, |ui| {
                        ui.spacing_mut().item_spacing = egui::vec2(4.0 as f32, 4.0 as f32);
                        ui.vertical(|ui| {
                            ui.label(egui::RichText::new("12,408 files, 3,102 notes. No .obsidian or .notescraft folder found, so this may not be a vault.").size(14.0 as f32).color(tokens::TEXT));
                            ui.label(egui::RichText::new("Opening adds a .notescraft folder here.").size(14.0 as f32).color(tokens::TEXT_MUTED));
                        });
                    });
                    egui::Frame::new().show(ui, |ui| {
                        ui.spacing_mut().item_spacing = egui::vec2(8.0 as f32, 8.0 as f32);
                        ui.horizontal(|ui| {
                            egui::Frame::new().fill(tokens::SURFACE_2).corner_radius(12.0 as f32).inner_margin(egui::Margin { top: 12, right: 16, bottom: 12, left: 16 }).show(ui, |ui| {
                                ui.spacing_mut().item_spacing = egui::vec2(8.0 as f32, 8.0 as f32);
                                ui.horizontal(|ui| {
                                    ui.label(egui::RichText::new("Cancel").size(14.0 as f32).color(tokens::TEXT).strong());
                                });
                            });
                            egui::Frame::new().fill(tokens::ACCENT).corner_radius(12.0 as f32).inner_margin(egui::Margin { top: 12, right: 16, bottom: 12, left: 16 }).show(ui, |ui| {
                                ui.spacing_mut().item_spacing = egui::vec2(8.0 as f32, 8.0 as f32);
                                ui.horizontal(|ui| {
                                    ui.label(egui::RichText::new("Open anyway").size(14.0 as f32).color(tokens::ON_ACCENT).strong());
                                });
                            });
                        });
                    });
                });
            });
        });
    });
}