Merge #262: M7: follow system theme from the first frame (@VanTan666)

# Conflicts:
#	contributors/people.toml
#	docs/ui-parity.md
This commit is contained in:
bflatastic
2026-10-15 15:10:11 +07:00
5 changed files with 98 additions and 3 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading (#207), SVG import budgets (#190), Leptos code (#223), full screen (#227), server snapshots readable again (#188), owner-only preferences (#200), ruzstd 0.9.1 (#182), layer cycle repair (#205), batch rollback, Direct3D 12 on Windows, plugin metering (#192); vector edit mode regions: hover hatch, selection and drag; SVG/PDF exports refuse over-depth documents (#208); bounded MCP/control framing, connections and queues (#201); plugin log limits (#191); live-session revert guard (#198); attached MCP no longer replays after a lost reply (#194); repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; starter templates, a Templates tab and Getting started tips on the home screen; your online files; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (system theme on first run; recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading (#207), SVG import budgets (#190), Leptos code (#223), full screen (#227), server snapshots readable again (#188), owner-only preferences (#200), ruzstd 0.9.1 (#182), layer cycle repair (#205), batch rollback, Direct3D 12 on Windows, plugin metering (#192); vector edit mode regions: hover hatch, selection and drag; SVG/PDF exports refuse over-depth documents (#208); bounded MCP/control framing, connections and queues (#201); plugin log limits (#191); live-session revert guard (#198); attached MCP no longer replays after a lost reply (#194); repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; starter templates, a Templates tab and Getting started tips on the home screen; your online files; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7)
FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow,
native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through
@@ -313,6 +313,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | System theme on first run (M7): preferences without a theme default to System; the first frame reads the OS appearance directly, including when returning from a manual theme. Saved Light/Dark choices stay explicit; unavailable OS appearance falls back to Light. Numbers unchanged. |
| 2026-10-14 | minor | Home screen recent files as cards (M7): each card shows the picture its file carries on its first page's colour, the file's icon, name and "Edited ... ago"; sort by name, creation or last change either way; a table view; a click selects, a double-click or Enter opens, arrow keys move; Open, Show in folder and Remove from recents on right-click. Numbers unchanged. |
| 2026-10-14 | minor | Lost images reported on loading (#207): a `.figcraft` file whose image member is damaged, too large or missing no longer loads in silence; each lost image (up to 20, then a count) is listed in `repairs` on `file.open` with its hash and the reason, and the app shows it as a toast. Saving still writes the file without the lost images. Numbers unchanged |
| 2026-10-14 | minor | SVG import budgets (#190): gzipped SVG is unpacked by FigCraft within 64 MiB before parsing (it was unpacked without a limit), so the nesting check sees the real document; SVG documents embedded in `<image>` data URIs share one 32 MiB budget and at most 4 levels, and one past a budget is left out. Linked files stay unread. Numbers unchanged |
+3
View File
@@ -20,3 +20,6 @@
[people.tylerjenningsw]
real_name = "Tyler Jennings"
display_name = "tylerjenningsw"
[people.VanTan666]
display_name = "VanTan"
+2 -1
View File
@@ -560,6 +560,7 @@ pub struct Prefs {
pub highlight_on_hover: bool,
pub number_keys_opacity: bool,
pub show_dimensions: bool,
/// "system" by default; an explicit "light" or "dark" choice stays saved.
pub theme: String,
/// The widths of the main window's left and right panels (the interface keeps them within its
/// limits; other windows have their own while they are open).
@@ -619,7 +620,7 @@ impl Default for Prefs {
highlight_on_hover: true,
number_keys_opacity: true,
show_dimensions: true,
theme: "light".into(),
theme: "system".into(),
panel_widths: (240.0, 240.0),
language: "system".into(),
user_name: cmd::comment::DEFAULT_AUTHOR.into(),
+6 -1
View File
@@ -63,6 +63,9 @@ pub mod versions;
pub mod widgets;
pub mod windows;
#[cfg(test)]
mod theme_startup_tests;
#[cfg(test)]
mod a11y_tests;
#[cfg(test)]
@@ -909,7 +912,9 @@ impl FigApp {
}
let dark = match self.session.prefs.theme.as_str() {
"dark" => true,
"system" => ctx.global_style().visuals.dark_mode,
// The active visuals were set by us last frame (possibly for a manual theme).
// Read the integration's OS theme instead; without one, keep the light fallback.
"system" => ctx.system_theme() == Some(egui::Theme::Dark),
_ => false,
};
self.ui.dark = dark;
+85
View File
@@ -0,0 +1,85 @@
//! Startup and system-theme changes through the real UI frame, before any settling frames.
use egui::{Context, RawInput, Theme};
use figcraft_engine::{Prefs, Session};
use serde_json::json;
use crate::{FigApp, NoServices, theme::Tokens};
fn frame(ctx: &Context, app: &mut FigApp, system: Option<Theme>) {
let mut output = ctx.run_ui(RawInput { system_theme: system, ..Default::default() }, |ui| app.ui(ui));
output.textures_delta.clear();
}
#[test]
fn fresh_app_uses_system_dark_theme_on_its_first_frame() {
let ctx = Context::default();
let mut app = FigApp::new(Session::empty(), Box::new(NoServices));
frame(&ctx, &mut app, Some(Theme::Dark));
assert!(app.ui.dark, "a first run must use the OS dark theme before drawing");
assert_eq!(Tokens::get(&ctx), Tokens::dark());
assert_eq!(app.session.prefs.theme, "system", "following the OS must remain a preference, not a saved snapshot");
}
#[test]
fn first_run_and_preferences_without_a_theme_follow_system_light() {
for prefs in [Prefs::default(), serde_json::from_value(json!({"nudge_small": 2})).unwrap()] {
let ctx = Context::default();
let mut session = Session::empty();
session.prefs = prefs;
let mut app = FigApp::new(session, Box::new(NoServices));
frame(&ctx, &mut app, Some(Theme::Light));
assert_eq!(app.session.prefs.theme, "system");
assert_eq!(Tokens::get(&ctx), Tokens::light());
}
}
#[test]
fn saved_manual_themes_survive_startup_and_os_changes() {
for (saved, expected) in [("light", Tokens::light()), ("dark", Tokens::dark())] {
let ctx = Context::default();
let mut session = Session::empty();
session.prefs = serde_json::from_value(json!({"theme": saved})).unwrap();
let mut app = FigApp::new(session, Box::new(NoServices));
for system in [Theme::Dark, Theme::Light, Theme::Dark] {
frame(&ctx, &mut app, Some(system));
assert_eq!(Tokens::get(&ctx), expected);
assert_eq!(app.session.prefs.theme, saved);
}
}
}
#[test]
fn returning_to_system_reads_the_os_not_the_previously_drawn_palette() {
for (manual, os) in [("view.themeLight", Theme::Dark), ("view.themeDark", Theme::Light)] {
let ctx = Context::default();
let mut app = FigApp::new(Session::empty(), Box::new(NoServices));
app.session.execute(manual, &json!({})).unwrap();
frame(&ctx, &mut app, Some(os));
app.session.execute("view.themeSystem", &json!({})).unwrap();
frame(&ctx, &mut app, Some(os));
assert_eq!(app.ui.dark, os == Theme::Dark);
assert_eq!(Tokens::get(&ctx).dark, os == Theme::Dark);
}
}
#[test]
fn system_theme_changes_apply_in_the_same_frame() {
let ctx = Context::default();
let mut app = FigApp::new(Session::empty(), Box::new(NoServices));
app.session.execute("view.themeSystem", &json!({})).unwrap();
for os in [Theme::Dark, Theme::Light, Theme::Dark] {
frame(&ctx, &mut app, Some(os));
assert_eq!(Tokens::get(&ctx).dark, os == Theme::Dark);
assert_eq!(app.session.prefs.theme, "system");
}
}
#[test]
fn unavailable_system_theme_uses_the_existing_light_fallback() {
let ctx = Context::default();
let mut app = FigApp::new(Session::empty(), Box::new(NoServices));
app.session.execute("view.themeSystem", &json!({})).unwrap();
frame(&ctx, &mut app, None);
assert_eq!(Tokens::get(&ctx), Tokens::light());
}