5e8020aa38
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>
17 KiB
17 KiB
AI UI Implementation Spec: Dialog / Large folder warning (egui)
1. Mandatory Enforcement Rules
- MUST NOT use
position: absolute(or Tailwindabsolute) on in-flow children (15 flow nodes under root) - MUST use token
--canvas(ortokens::CANVAS) instead of hardcoded hex#11111bonDialog / 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(ortokens::SURFACE) instead of hardcoded hex#181825onDialog / 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(ortokens::TEXT) instead of hardcoded hex#cdd6f4onOpen this large folder?(#790) - MUST include exact text string "Open this large folder?" for
Open this large folder?(#790) - MUST use token
--bg(ortokens::BG) instead of hardcoded hex#1e1e2eonFolder Path(#791) - MUST use row auto-layout on
Folder Path(#791) with gap 8px and padding [8px, 12px, 8px, 12px] - MUST use token
--text(ortokens::TEXT) instead of hardcoded hex#cdd6f4on/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(ortokens::TEXT) instead of hardcoded hex#cdd6f4on12,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(ortokens::TEXT_MUTED) instead of hardcoded hex#a6adc8onOpening 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) referencingButton / Secondary - MUST use token
--surface-2(ortokens::SURFACE_2) instead of hardcoded hex#313244onButton / Secondary(#799) - MUST use row auto-layout on
Button / Secondary(#799) with gap 8px and padding [12px, 16px, 12px, 16px] - MUST use token
--text(ortokens::TEXT) instead of hardcoded hex#cdd6f4onSecondary(#800) - MUST include exact text string "Cancel" for
Secondary(#800) - MUST preserve component instance
Button / Primary(#801) referencingButton / Primary - MUST use token
--accent(ortokens::ACCENT) instead of hardcoded hex#cba6f7onButton / 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(ortokens::ON_ACCENT) instead of hardcoded hex#11111bonPrimary 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());
});
});
});
});
});
});
});
});
}