mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 05:46:46 -06:00
The browser build's wasm was 25,982,540 bytes, past the gate raised for 0.12.1. It is now
23,915,213 bytes (22.8 MiB), 1.2 MiB under the restored 24 MiB gate:
- Catalog rows keep their plural forms behind a reference: Text is two words, not room for
four strings, across ~52,000 baked-in rows (-454 KB).
- The browser build compiles with wasm SIMD (apps/figcraft-web/.cargo/config.toml, wasm-opt
--enable-simd), so egui's glyph rasterizer leaves out its scalar SIMD fallback (-1.6 MB).
The plugin workspace is built from the repository root and stays without SIMD.
- Revert the gate raise (cb227c97); the hosting guide names the WebAssembly SIMD requirement.
This commit is contained in:
+1
-1
@@ -136,7 +136,7 @@ lto = "thin"
|
||||
codegen-units = 4
|
||||
|
||||
# The browser build only (apps/figcraft-web/index.html: data-cargo-profile). Optimised for size:
|
||||
# packaging/web/package.sh fails a .wasm over 25 MiB - 512 KiB (Cloudflare's per-file cap is 25 MiB).
|
||||
# packaging/web/package.sh fails a .wasm over 24 MiB (Cloudflare's per-file cap is 25 MiB).
|
||||
# wasm32-unknown-unknown always aborts on panic, so panic = "abort" changes nothing there.
|
||||
[profile.web-release]
|
||||
inherits = "release"
|
||||
|
||||
+2
-1
@@ -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-29 · **Change:** minor (shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); offline Penpot 2.x import/export (#1143); optional batch continue-on-error with per-step outcomes (#1386); the Design panel's sections on one grid (#858); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); images placed at their own size, 2x screenshots in points (#1360); the toolbar on one 32 pt grid, without its Resources button (#1210); M4: zero-length round-capped dashes draw dots; dash expansion uses the existing safety budget; extracted from #1143; Arabic draft catalog; scaled instances keep their scale when their main is rebuilt (#922); .fig embedded images are copied once per blob, not once per hash (#1059); page ruler guides survive save and reopen (#1476); colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); offline Penpot 2.x import/export (#1143); optional batch continue-on-error with per-step outcomes (#1386); the Design panel's sections on one grid (#858); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); images placed at their own size, 2x screenshots in points (#1360); the toolbar on one 32 pt grid, without its Resources button (#1210); M4: zero-length round-capped dashes draw dots; dash expansion uses the existing safety budget; extracted from #1143; Arabic draft catalog; scaled instances keep their scale when their main is rebuilt (#922); .fig embedded images are copied once per blob, not once per hash (#1059); page ruler guides survive save and reopen (#1476); colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **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
|
||||
@@ -355,6 +355,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-29 | minor | Web wasm size (#1472): the release wasm is 23,915,213 bytes (22.8 MiB), down from 25,982,540 (24.8 MiB), 1.2 MiB under the gate. Catalog rows hold their plural forms behind a reference (two words, not eight) and the browser build compiles with wasm SIMD, so the glyph rasterizer's scalar fallback is left out; the 24 MiB gate in `packaging/web/package.sh` is restored. The browser now needs WebAssembly SIMD (Safari 16.4 or later). Numbers unchanged. |
|
||||
| 2026-10-29 | minor | Shadows of images with transparent pixels (M3, #1289): a drop shadow is cast from the picture's alpha and hidden only behind its pixels, not its whole box (a transparent PNG's shadow was cut away inside its bounding box), unless shown behind transparent areas; its inner shadow shades the inside of its alpha, not the box's edge; the same for Fit images, crops smaller than the box and pattern fills. Opaque pictures that fill their box keep their outline (spread still offsets it exactly); each image is scanned for transparency once. Canvas, PNG, SVG (knocked out by the layer's alpha; an inner shadow embeds a render) and PDF (`image_shadow_tests` in render and svg). Numbers unchanged |
|
||||
| 2026-10-29 | minor | M1, #1464: Crop image. Object ▸ Crop image, the right-click menu (one image layer) and a crop button on the image fill row run `image.crop`: the layer's top visible image fill (or the one `index` names) switches to Crop with the picture left where Fill or Fit drew it, as one undo step, and the canvas enters crop mode (the image shows dimmed outside the layer, whose box gets corner brackets and edge bars; the layer's edges and corners crop it with the picture held still, the image drags to move and its corners scale it; Enter, Escape or a click outside finishes). A layer without an image fill, or several layers, are refused with a reason. Tests: `image_crop_tests`, `crop_image_tests`. Numbers unchanged (the menu item is FigCraft's own and not counted by `parity`) |
|
||||
| 2026-10-29 | minor | The Design panel's sections on one grid (M4, #858): 16 pt left and 8 pt right margins, two 88 pt columns with 8 pt gutters and a 24 pt button column, 24 pt rows under 10 pt captions, 4 pt between a row's two buttons; section titles stay 11 pt semibold; the panel's edge is drawn inside it, so its right margin no longer loses 1 pt to a frame stroke. Numbers unchanged |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# The browser build (trunk runs cargo from this folder; packaging/web/package.sh). With simd128 the
|
||||
# glyph rasterizer's SIMD code compiles for wasm SIMD instead of its much larger scalar fallback:
|
||||
# about 1.6 MB less wasm. Every current browser has wasm SIMD (Safari since 16.4). The plugin
|
||||
# workspace is built from the repository root, so its modules stay without SIMD for the plugin runtime.
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["-Ctarget-feature=+simd128"]
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover">
|
||||
<title>FigCraft</title>
|
||||
<link data-trunk rel="rust" data-bin="figcraft-web" data-cargo-profile="web-release" data-wasm-opt="z" data-wasm-opt-params="--enable-bulk-memory --enable-bulk-memory-opt --enable-nontrapping-float-to-int --enable-sign-ext --enable-reference-types --enable-multivalue" data-weak-refs />
|
||||
<link data-trunk rel="rust" data-bin="figcraft-web" data-cargo-profile="web-release" data-wasm-opt="z" data-wasm-opt-params="--enable-bulk-memory --enable-bulk-memory-opt --enable-nontrapping-float-to-int --enable-sign-ext --enable-reference-types --enable-multivalue --enable-simd" data-weak-refs />
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; overscroll-behavior: none; background: #f5f5f5; }
|
||||
#figcraft_canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }
|
||||
|
||||
@@ -84,7 +84,7 @@ fn table(o: &mut String, path: &std::path::Path, t: Option<&toml::Value>) {
|
||||
(None, one, Some(other)) => {
|
||||
_ = writeln!(
|
||||
o,
|
||||
" ({k:?}, Text::Plural {{ one: {}, few: {}, many: {}, other: {other:?} }}),",
|
||||
" ({k:?}, Text::Plural(&Forms {{ one: {}, few: {}, many: {}, other: {other:?} }})),",
|
||||
name(one),
|
||||
name(s("few")),
|
||||
name(s("many")),
|
||||
|
||||
@@ -651,14 +651,14 @@ const GLASS_FIELD_W: f32 = 87.5;
|
||||
/// value.
|
||||
#[test]
|
||||
fn the_intensity_label_fits_whole_beside_its_value() {
|
||||
use crate::i18n::{self, Text};
|
||||
use crate::i18n::{self, Forms, Text};
|
||||
let ctx = egui::Context::default();
|
||||
let mut out = ctx.run_ui(Default::default(), |ui| {
|
||||
let width = |s: &str| ui.painter().layout_no_wrap(s.to_string(), crate::theme::regular(11.0), egui::Color32::WHITE).size().x;
|
||||
let mut english = 0.0;
|
||||
for lang in i18n::languages() {
|
||||
let label = lang.ui.iter().find(|(k, _)| *k == "Intensity").map_or("Intensity", |(_, t)| match t {
|
||||
Text::Plain(s) | Text::Plural { other: s, .. } => s,
|
||||
Text::Plain(s) | Text::Plural(Forms { other: s, .. }) => s,
|
||||
});
|
||||
// The test's context has only egui's own font: a label it can't draw (Vietnamese
|
||||
// diacritics, CJK) can't be measured here, and the app draws it with a system font.
|
||||
|
||||
+15
-12
@@ -10,16 +10,19 @@ use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
/// One catalog entry.
|
||||
/// One catalog entry. Two words: the plural forms sit behind a reference, so the ~52,000 baked-in
|
||||
/// rows don't each carry four strings' worth of space (about 450 KB of the browser build).
|
||||
pub enum Text {
|
||||
Plain(&'static str),
|
||||
/// Optional language-specific plural forms, with `other` as the fallback.
|
||||
Plural {
|
||||
one: Option<&'static str>,
|
||||
few: Option<&'static str>,
|
||||
many: Option<&'static str>,
|
||||
other: &'static str,
|
||||
},
|
||||
Plural(&'static Forms),
|
||||
}
|
||||
|
||||
/// Optional language-specific plural forms, with `other` as the fallback.
|
||||
pub struct Forms {
|
||||
pub one: Option<&'static str>,
|
||||
pub few: Option<&'static str>,
|
||||
pub many: Option<&'static str>,
|
||||
pub other: &'static str,
|
||||
}
|
||||
|
||||
/// A language's catalog (generated from `locales/<code>.toml`).
|
||||
@@ -116,7 +119,7 @@ pub fn tr(key: &str) -> &str {
|
||||
/// `None` when the catalog doesn't have it.
|
||||
pub fn try_tr(key: &str) -> Option<&str> {
|
||||
match lookup(CURRENT.load(Ordering::Relaxed), key)? {
|
||||
Text::Plain(s) | Text::Plural { other: s, .. } => Some(s),
|
||||
Text::Plain(s) | Text::Plural(Forms { other: s, .. }) => Some(s),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +150,7 @@ fn counted(lang: usize, key: &str, n: usize) -> &str {
|
||||
.find_map(|l| {
|
||||
find(l.ui, key).map(|text| match text {
|
||||
Text::Plain(s) => *s,
|
||||
Text::Plural { one, few, many, other } => match plural(l.code, n) {
|
||||
Text::Plural(Forms { one, few, many, other }) => match plural(l.code, n) {
|
||||
Plural::One => one.unwrap_or(other),
|
||||
Plural::Few => few.unwrap_or(other),
|
||||
Plural::Many => many.unwrap_or(other),
|
||||
@@ -234,8 +237,8 @@ pub fn menu_item<'a>(id: &str, english: &'a str) -> Cow<'a, str> {
|
||||
fn label<'a>(menu: bool, id: &str, english: &'a str) -> Cow<'a, str> {
|
||||
let text = chain(CURRENT.load(Ordering::Relaxed)).find_map(|l| menu.then(|| find(l.menu, id)).flatten().or_else(|| find(l.commands, id)));
|
||||
match text {
|
||||
Some(Text::Plain(s) | Text::Plural { other: s, .. }) if english.ends_with('…') && !s.ends_with('…') => Cow::Owned(format!("{s}…")),
|
||||
Some(Text::Plain(s) | Text::Plural { other: s, .. }) => Cow::Borrowed(s),
|
||||
Some(Text::Plain(s) | Text::Plural(Forms { other: s, .. })) if english.ends_with('…') && !s.ends_with('…') => Cow::Owned(format!("{s}…")),
|
||||
Some(Text::Plain(s) | Text::Plural(Forms { other: s, .. })) => Cow::Borrowed(s),
|
||||
None => Cow::Borrowed(english),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::*;
|
||||
use super::{Forms, LANGS, Lang, Plural, Text, chain, counted, fill, find, lookup, plural, plural_one, resolve};
|
||||
|
||||
/// Keys looked up from data rather than written as `tr("…")` in the code: the main menu's
|
||||
/// submenu names (the engine's menu catalog) and labels of engine and document enums.
|
||||
@@ -261,10 +261,18 @@ fn placeholders(s: &str) -> BTreeSet<&str> {
|
||||
fn forms(t: &Text) -> Vec<&'static str> {
|
||||
match t {
|
||||
Text::Plain(s) => vec![s],
|
||||
Text::Plural { one, few, many, other } => one.iter().chain(few).chain(many).copied().chain([*other]).collect(),
|
||||
Text::Plural(Forms { one, few, many, other }) => one.iter().chain(few).chain(many).copied().chain([*other]).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The browser build bakes in every catalog: a row is a key and a two-word entry, not an entry
|
||||
/// with room for four strings.
|
||||
#[test]
|
||||
fn a_catalog_row_is_four_words() {
|
||||
assert_eq!(std::mem::size_of::<Text>(), 2 * std::mem::size_of::<usize>());
|
||||
assert_eq!(std::mem::size_of::<(&str, Text)>(), 4 * std::mem::size_of::<usize>());
|
||||
}
|
||||
|
||||
fn english() -> &'static Lang {
|
||||
LANGS.first().filter(|l| l.code == "en").expect("locales/en.toml is baked in first")
|
||||
}
|
||||
@@ -315,10 +323,10 @@ fn catalogs_have_known_keys_and_matching_placeholders() {
|
||||
for (i, form) in forms(t).iter().enumerate() {
|
||||
let got = placeholders(form);
|
||||
// A "one" form may spell the count out ("una capa").
|
||||
let ok = if matches!(t, Text::Plural { one: Some(_), .. }) && i == 0 { got.is_subset(&want) } else { got == want };
|
||||
let ok = if matches!(t, Text::Plural(Forms { one: Some(_), .. })) && i == 0 { got.is_subset(&want) } else { got == want };
|
||||
assert!(ok, "{}: {k:?} has placeholders {got:?}, English has {want:?}", lang.code);
|
||||
}
|
||||
assert_eq!(matches!(t, Text::Plural { .. }), matches!(src, Text::Plural { .. }), "{}: {k:?} plural or not as in English", lang.code);
|
||||
assert_eq!(matches!(t, Text::Plural(_)), matches!(src, Text::Plural(_)), "{}: {k:?} plural or not as in English", lang.code);
|
||||
}
|
||||
for (id, t) in lang.commands.iter().chain(lang.menu) {
|
||||
assert!(ids.contains(id), "{}: unknown command id {id:?}", lang.code);
|
||||
@@ -479,7 +487,7 @@ fn japanese_korean_chinese_and_vietnamese_have_no_plural_forms() {
|
||||
for code in ["ja", "ko", "th", "zh-Hans", "vi"] {
|
||||
let lang = LANGS.iter().position(|l| l.code == code).unwrap();
|
||||
assert!(!plural_one(lang, 0) && !plural_one(lang, 1) && !plural_one(lang, 2), "{code}");
|
||||
let ones: Vec<_> = LANGS[lang].ui.iter().filter(|(_, t)| matches!(t, Text::Plural { one: Some(_), .. })).map(|(k, _)| k).collect();
|
||||
let ones: Vec<_> = LANGS[lang].ui.iter().filter(|(_, t)| matches!(t, Text::Plural(Forms { one: Some(_), .. }))).map(|(k, _)| k).collect();
|
||||
assert!(ones.is_empty(), "{code}: plural entries give only `other`: {ones:?}");
|
||||
}
|
||||
}
|
||||
@@ -502,7 +510,7 @@ fn arabic_tags_and_count_neutral_forms() {
|
||||
}
|
||||
let counts = [0, 1, 2, 3, 10, 11, 99, 100, 102, 103, 111, 200, 1000];
|
||||
for (key, text) in LANGS[ar].ui {
|
||||
if let Text::Plural { one, few, many, other } = text {
|
||||
if let Text::Plural(Forms { one, few, many, other }) = text {
|
||||
assert!(one.is_none() && few.is_none() && many.is_none(), "{key}: count-neutral wording uses only other");
|
||||
for n in counts {
|
||||
assert_eq!(counted(ar, key, n), *other, "{key}, {n}");
|
||||
|
||||
+3
-1
@@ -195,9 +195,11 @@ tar -xzf figcraft-<v>-freebsd-x86_64.tar.gz --strip-components 1 -C /usr/local
|
||||
### Web
|
||||
|
||||
`packaging/web/package.sh` runs `trunk build --release` in `apps/figcraft-web` (`Trunk.toml`:
|
||||
`public_url = "./"`, output `dist/web`), fails if a `.wasm` exceeds 25 MiB minus 512 KiB, and zips the site
|
||||
`public_url = "./"`, output `dist/web`), fails if a `.wasm` exceeds 24 MiB, and zips the site
|
||||
with sample `_headers`/`.htaccess` and the hosting guide. The app uses WebGPU with a WebGL2
|
||||
fallback; `?webgl` forces WebGL2. 0.1.0: wasm 17.7 MiB raw, 6.7 MiB gzip, 4.4 MiB Brotli.
|
||||
`apps/figcraft-web/.cargo/config.toml` builds it with wasm SIMD (`+simd128`), which keeps the
|
||||
glyph rasterizer's scalar fallback out of the module.
|
||||
|
||||
The wasm is built with the size-optimised `web-release` Cargo profile (`opt-level = "s"`, fat
|
||||
LTO, one codegen unit; `data-cargo-profile` in `apps/figcraft-web/index.html`) and then
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# FigCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-28 · **Change:** minor (shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); M0.9: optional batch continue-on-error with per-step outcomes (#1386); Alt pressed during a drag duplicates (#1385); variables in paint props (M3, #1000); finding layers without selecting them (M0.9, part of #996); the attached MCP server reads the app's replies with a 512 MiB bound (#1113); a failed batch keeps the undo steps it undid (#1410); Arabic draft catalog; Android Vector Drawable export; a selected unfilled shape drags over a filled layer below it (#1370); text truncation fills the last line, survives resizing, and auto height takes Max lines (#1375, #1376, #1377); M11 widget fields and property menus; M12 Boards: timer and voting; Installed weights of an embedded font family (#1246); `.sketch` import, best effort; Adobe Fonts found on macOS (#298); M12 Boards; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-28 · **Change:** minor (the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); M0.9: optional batch continue-on-error with per-step outcomes (#1386); Alt pressed during a drag duplicates (#1385); variables in paint props (M3, #1000); finding layers without selecting them (M0.9, part of #996); the attached MCP server reads the app's replies with a 512 MiB bound (#1113); a failed batch keeps the undo steps it undid (#1410); Arabic draft catalog; Android Vector Drawable export; a selected unfilled shape drags over a filled layer below it (#1370); text truncation fills the last line, survives resizing, and auto height takes Max lines (#1375, #1376, #1377); M11 widget fields and property menus; M12 Boards: timer and voting; Installed weights of an embedded font family (#1246); `.sketch` import, best effort; Adobe Fonts found on macOS (#298); M12 Boards; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
Forward-looking plan. Where FigCraft stands today: [`ROADMAP.md`](../ROADMAP.md) and
|
||||
[`target-app-parity.md`](target-app-parity.md); the full ranked list: [`gaps.md`](gaps.md). Hours
|
||||
@@ -75,6 +75,7 @@ blockers.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-29 | minor | Web wasm size (#1472): the release wasm is 23,915,213 bytes (22.8 MiB), down from 25,982,540 (24.8 MiB), 1.2 MiB under the gate. Catalog rows hold their plural forms behind a reference (two words, not eight) and the browser build compiles with wasm SIMD, so the glyph rasterizer's scalar fallback is left out; the 24 MiB gate in `packaging/web/package.sh` is restored. The browser now needs WebAssembly SIMD (Safari 16.4 or later). Numbers unchanged. |
|
||||
| 2026-10-29 | minor | Shadows of images with transparent pixels (M3, #1289): a drop shadow is cast from the picture's alpha and hidden only behind its pixels, not its whole box (a transparent PNG's shadow was cut away inside its bounding box), unless shown behind transparent areas; its inner shadow shades the inside of its alpha, not the box's edge; the same for Fit images, crops smaller than the box and pattern fills. Opaque pictures that fill their box keep their outline (spread still offsets it exactly); each image is scanned for transparency once. Canvas, PNG, SVG (knocked out by the layer's alpha; an inner shadow embeds a render) and PDF (`image_shadow_tests` in render and svg). Numbers unchanged |
|
||||
| 2026-10-29 | minor | M1, #1464: Crop image. Object ▸ Crop image, the right-click menu (one image layer) and a crop button on the image fill row run `image.crop`: the layer's top visible image fill (or the one `index` names) switches to Crop with the picture left where Fill or Fit drew it, as one undo step, and the canvas enters crop mode (the image shows dimmed outside the layer, whose box gets corner brackets and edge bars; the layer's edges and corners crop it with the picture held still, the image drags to move and its corners scale it; Enter, Escape or a click outside finishes). A layer without an image fill, or several layers, are refused with a reason. Tests: `image_crop_tests`, `crop_image_tests`. Numbers unchanged (the menu item is FigCraft's own and not counted by `parity`) |
|
||||
| 2026-10-28 | minor | Arabic draft: 1,857 UI strings, 620 command labels, 31 menu wordings; count-neutral plurals and web font gate. Native review and panel mirroring remain open; grade and estimates unchanged. Evidence: `crates/ui-egui/locales/ar.toml`, i18n catalog tests and [translation notes](translating.md#arabic-draft) |
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ not yet reviewed by a native speaker. Existing language catalogs are unchanged.
|
||||
The catalog uses Modern Standard Arabic, logical Unicode order, Western digits, and Arabic
|
||||
punctuation. Product names, command ids, keyboard glyphs and `{placeholders}` are preserved.
|
||||
English plural entries give only `other`, with count-neutral wording such as `عدد الطبقات: {n}`.
|
||||
This is deliberate: the current `Text::Plural` has no zero/two fields. It is not an implementation
|
||||
This is deliberate: the current plural `Forms` have no zero/two fields. It is not an implementation
|
||||
of Arabic's six CLDR categories; wording remains valid for 0, 1, 2 and larger integer counts.
|
||||
|
||||
Arabic text uses the existing bidi/shaping and Arabic system-font fallback on desktop. Web builds
|
||||
|
||||
@@ -16,7 +16,7 @@ that serves static files.
|
||||
|
||||
The editor runs entirely on the visitor's device. The web build starts with one new document;
|
||||
there is no desktop TCP control server, and native file dialogs aren't wired up yet. A browser
|
||||
with WebGPU or WebGL2 is required.
|
||||
with WebGPU or WebGL2 and WebAssembly SIMD is required (any current browser; Safari 16.4 or later).
|
||||
|
||||
## Live sessions
|
||||
|
||||
@@ -37,7 +37,7 @@ Measured on the 0.1.0 build (`trunk build --release`; gzip `-9`, Brotli quality
|
||||
|
||||
Per-file upload limits: Cloudflare Pages and Workers static assets reject any file over
|
||||
**25 MiB** (26,214,400 bytes). `packaging/web/package.sh` fails the build if the wasm ever grows
|
||||
past 25 MiB minus 512 KiB (25,690,112 bytes), so a release can't ship a file a common host refuses.
|
||||
past 24 MiB, so a release can't ship a file a common host refuses.
|
||||
|
||||
## Any path works
|
||||
|
||||
|
||||
@@ -27,9 +27,7 @@ fi
|
||||
# Size gate: Cloudflare Pages/Workers reject any single file over 25 MiB, and other
|
||||
# hosts and CDNs have similar caps. Fail well before that so a regression shows up here, not at
|
||||
# upload time. Override with FIGCRAFT_WASM_MAX_BYTES only to investigate.
|
||||
# 25 MiB - 512 KiB: raised from 24 MiB for 0.12.1, when egui 0.37 and new features took the wasm to
|
||||
# 25.3 MB with opt-level z and wasm-opt -Oz already applied. Cutting the wasm back down is open work.
|
||||
MAX_WASM_BYTES="${FIGCRAFT_WASM_MAX_BYTES:-25690112}" # 25 MiB - 512 KiB
|
||||
MAX_WASM_BYTES="${FIGCRAFT_WASM_MAX_BYTES:-25165824}" # 24 MiB
|
||||
WASM_COUNT=0
|
||||
for wasm in "$SITE"/*.wasm; do
|
||||
[[ -f "$wasm" ]] || continue
|
||||
|
||||
Reference in New Issue
Block a user