Dragging a pane (a docked pane's title or a floating pane) over another
pane docked at a side edge offers two dock zones: the outer third docks
it directly outside that pane, the inner third directly inside it. The
middle third is no target. The window-edge strip docks a pane outermost
on that edge. The new PaneRequest::Before(pane, before) asks the app to
move the pane to directly before another in its open list, which sets
its position on the edge; the drop preview is laid out from that order,
so it matches where the pane lands. With one slot per edge, a closable
pane dropped on an edge takes the slot.
DockContent::group_extra_tabs_width and group_extra_tabs draw host tabs
right after a group's panel tabs, before the drag bar and the header.
They fit with the panel tabs as one more tab that never overflows: both
shrink widest first to min_tab_width, and in a narrower row the panel
tabs overflow while the extra tabs keep that width.
* Add craft-menu: Mac menu-bar layout and native-menu driver
A platform-free menu model (built directly or from flat rows), the macOS
layout (app menu, Window, Help, system-key clash resolution, translatable
labels), and NativeMenu, which feeds a backend's key equivalents to egui as
key presses, runs Minimize and craft-quit's Quit, and re-syncs only when the
app's state changes. Generalised from PhotoCraft's native_menu.rs.
* craft-menu: macOS menu bar through muda
* craft-menu: clipboard keys, layout-assigned shortcuts, Hide Others and atomic rebuilds
* craft-menu: menu key equivalents run while a text field has the keys
* craft-menu: commands as CommandRef, item kinds, craft-keymap chords
Items and rows run a craft_keymap::CommandRef and take a craft_keymap::Chord
shortcut; crates/menu/src/chord.rs is gone. Native ids are the command's key,
made unique with #2, #3 when a command is shown twice. Item.checked becomes
ItemKind { Plain, Check, Radio } (radio shows as a check item on macOS).
The built-in items' commands are Builtin::{Hide, HideOthers, Minimize, Quit}.
MenuRole gains Edit, View and Other; tag_submenu finds its menu by role.
While text editing, the keys left to the field are the clipboard keys and
craft_keymap::text_editing_chords. Adds the Platforms, Document and Gate types
(data only) and MenuBar::documents.
* craft-menu: Gate::allows, the one rule for what may run from the menus
* craft-menu: Windows, Linux and web layouts, documents in Window, keymap adapter
layout(bar, Target, options) dispatches to mac_layout, desktop_layout or
web_layout. Every layout drops what the target's Platforms leave out and lists
MenuBar::documents as radio items last in the Window menu (ItemRole::Document
is gone). Windows and Linux put About in Help, Settings in Edit as
Preferences and Quit in File (Exit on Windows); the web layout has no Quit and
takes the browser's own shortcuts off the items, reporting clashes. tidy drops
empty submenus.
apply_keymap sets shortcuts and toggle check items from a craft-keymap Keymap;
apply_state sets enabled and checked per frame.
* craft-menu: the egui-drawn menu bar for Windows, Linux and the web
* craft-menu: gate, command hook and one muda handler for the native menu
- NativeMenu::set_gate: app items greyed in place while modal or recording a
shortcut; take_clicks drops clicks the gate forbids when they are taken.
While recording, key equivalents reach egui as plain key presses.
- NativeMenu::on_command: a hook called from the menu event handler for each
allowed app click; take_clicks still returns every command.
- Dispatch, shared between the driver and the backend's handler
(Backend::attach replaces Backend::set_text_editing).
- Only Cmd or Ctrl chords become key equivalents; other shortcuts are not
shown natively (building an attributed title needs unsafe objc2 calls).
- mac: one muda event handler per process, routed to the latest install;
mac::install_app_menu ports craft-quit's minimal app menu onto it.
- craft_quit::install_app_menu is deprecated in favor of
craft_menu::mac::install_app_menu.
* craft-menu: the bar takes Target, README section and NOTICE credit
* craft-menu: bar tests beside the layout and native ones; only a click outside closes the menus
* craft-menu: clippy (complex types, Copy clones, elided lifetimes)
* craft-menu: keyboard access to the egui bar
A tap on Alt alone or F10 focuses the bar, Alt+letter opens a menu and
a letter in an open menu runs or opens the row with that mnemonic, as
in a Windows menu bar. Mnemonics are assigned per level from the shown
labels, underlined only while the keyboard drives the bar, and a label
without an ASCII letter shows its English letter after it ("文件(F)")
through the optional english hook. Preferred letters come from
MenuBarUi::mnemonics. A Mac target has none, and a dialog or the
shortcut recorder keeps the keys. Ported from PhotoCraft's menu_keys.rs
and menu_keys_tests.rs.
* craft-menu: one installer or the other; sort mnemonics before checking uniqueness
* craft-menu: egui 0.37
* craft-menu: text fields keep their Edit items; web names by the browser's system; sync per frame; stable check kinds; handled hook clicks
* craft-menu: sync reports a clash change on an unchanged bar
* craft-menu: shrink-to-fit bar, bar runs Quit and Minimize, mnemonic before an ellipsis documented; craft-keymap: parse the minus sign
* craft-keymap: test the arrow glyphs as keys
* craft-menu: mac::install_with and MacOptions::window_list, for apps that list their documents themselves
* craft-menu: a negative fit limit draws no bar; Window list docs name the Window menu
A native floating window is an egui pass of its own, and can draw several
panel bodies. A Composer drawn there records its descriptor, and the dock
area publishes it once, after the window's last body, so a body drawn after
the editor's cannot clear it and end the composition. DockContent::window_drawn
runs at the same point for apps that publish their own descriptor.
* Add craft-menu: Mac menu-bar layout and native-menu driver
A platform-free menu model (built directly or from flat rows), the macOS
layout (app menu, Window, Help, system-key clash resolution, translatable
labels), and NativeMenu, which feeds a backend's key equivalents to egui as
key presses, runs Minimize and craft-quit's Quit, and re-syncs only when the
app's state changes. Generalised from PhotoCraft's native_menu.rs.
* craft-menu: macOS menu bar through muda
* craft-menu: clipboard keys, layout-assigned shortcuts, Hide Others and atomic rebuilds
* craft-menu: menu key equivalents run while a text field has the keys
* craft-menu: commands as CommandRef, item kinds, craft-keymap chords
Items and rows run a craft_keymap::CommandRef and take a craft_keymap::Chord
shortcut; crates/menu/src/chord.rs is gone. Native ids are the command's key,
made unique with #2, #3 when a command is shown twice. Item.checked becomes
ItemKind { Plain, Check, Radio } (radio shows as a check item on macOS).
The built-in items' commands are Builtin::{Hide, HideOthers, Minimize, Quit}.
MenuRole gains Edit, View and Other; tag_submenu finds its menu by role.
While text editing, the keys left to the field are the clipboard keys and
craft_keymap::text_editing_chords. Adds the Platforms, Document and Gate types
(data only) and MenuBar::documents.
* craft-menu: Gate::allows, the one rule for what may run from the menus
* craft-menu: Windows, Linux and web layouts, documents in Window, keymap adapter
layout(bar, Target, options) dispatches to mac_layout, desktop_layout or
web_layout. Every layout drops what the target's Platforms leave out and lists
MenuBar::documents as radio items last in the Window menu (ItemRole::Document
is gone). Windows and Linux put About in Help, Settings in Edit as
Preferences and Quit in File (Exit on Windows); the web layout has no Quit and
takes the browser's own shortcuts off the items, reporting clashes. tidy drops
empty submenus.
apply_keymap sets shortcuts and toggle check items from a craft-keymap Keymap;
apply_state sets enabled and checked per frame.
* craft-menu: the egui-drawn menu bar for Windows, Linux and the web
* craft-menu: gate, command hook and one muda handler for the native menu
- NativeMenu::set_gate: app items greyed in place while modal or recording a
shortcut; take_clicks drops clicks the gate forbids when they are taken.
While recording, key equivalents reach egui as plain key presses.
- NativeMenu::on_command: a hook called from the menu event handler for each
allowed app click; take_clicks still returns every command.
- Dispatch, shared between the driver and the backend's handler
(Backend::attach replaces Backend::set_text_editing).
- Only Cmd or Ctrl chords become key equivalents; other shortcuts are not
shown natively (building an attributed title needs unsafe objc2 calls).
- mac: one muda event handler per process, routed to the latest install;
mac::install_app_menu ports craft-quit's minimal app menu onto it.
- craft_quit::install_app_menu is deprecated in favor of
craft_menu::mac::install_app_menu.
* craft-menu: the bar takes Target, README section and NOTICE credit
* craft-menu: bar tests beside the layout and native ones; only a click outside closes the menus
* craft-menu: clippy (complex types, Copy clones, elided lifetimes)
* craft-menu: keyboard access to the egui bar
A tap on Alt alone or F10 focuses the bar, Alt+letter opens a menu and
a letter in an open menu runs or opens the row with that mnemonic, as
in a Windows menu bar. Mnemonics are assigned per level from the shown
labels, underlined only while the keyboard drives the bar, and a label
without an ASCII letter shows its English letter after it ("文件(F)")
through the optional english hook. Preferred letters come from
MenuBarUi::mnemonics. A Mac target has none, and a dialog or the
shortcut recorder keeps the keys. Ported from PhotoCraft's menu_keys.rs
and menu_keys_tests.rs.
* craft-menu: one installer or the other; sort mnemonics before checking uniqueness
* craft-menu: egui 0.37
* craft-menu: text fields keep their Edit items; web names by the browser's system; sync per frame; stable check kinds; handled hook clicks
* craft-menu: sync reports a clash change on an unchanged bar
* docking: AppKit moves a native window for its bar drag on macOS
Moving a native window with OuterPosition every frame jitters on macOS:
the window's reported place and the pointer events disagree about where
the window is. On macOS the bar press (or the start of a drag of a
window's only tab or group) now sends ViewportCommand::StartDrag, which
winit turns into performWindowDragWithEvent, and the dock area sends no
OuterPosition for that drag.
egui keeps running frames during the move, but the window's reported
place lags it, so each frame takes the window's place as AppKit's
cursor (NSEvent::mouseLocation) less the pressed point. Drop targets,
the drag fade, cross-window routing and the drop use that point. The
drag ends on the first frame where NSEvent::pressedMouseButtons or egui
reports the release, and is resolved once; a desktop drop records
cursor less grab. Ctrl/Cmd is read from AppKit's modifier flags, since
AppKit sends no key events during the move, and Escape does not cancel
the move. A press released within the click distance docks nothing.
Windows, X11 and Wayland keep their paths.
The window system seam (WindowSystem, formerly DesktopCursor) tells the
cursor, the button, the modifiers and whether it moves windows.
NativeHost models AppKit's move (moves_windows, held): the window
follows the cursor at the grab and reports its place late. The
app-driven cursor path from #325 and its stale-pointer model are
removed, since macOS no longer takes that path.
* docs: a cancel during an AppKit bar drag leaves the window moving
- proof: pass PoisonError::into_inner instead of an identity closure (S1612).
- layout tests: replace glob super::* imports with explicit names (S2208).
- packaging/macos/import-cert.sh: use [[ for the status test (S7688).
Wall-clock bounds of 1-2 s failed on loaded runners. Each bound is now 10 s, at least 10x the
fixed code's time and well under the old code's on the same input (measured by running the
pre-fix code):
- self-continuing rules: 40,000 rules (was 16,000); ~1 ms now, old checker 14 s at 16,000.
- many strips per suffix: 200,000 rules (was 30,000); 0.4 s now, old index 3 s at 30,000 and
11 min at 300,000.
- forms of self-continuing rules: the MAX_FORMS count catches the old code (9 million forms);
the time bound is a backstop.
- overlong Turkish words: ~20 ms now; without the caps it ran past 5 minutes.
- capped Hunspell reads: no clock; the "not a regular file" error is the proof (the old reader
never returned from /dev/zero).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
Asked to confirm a non-UTF-8 Turkish dictionary still loads: before this PR
tr_TR was read with read_to_string, so a non-UTF-8 file never loaded; now it
goes through read_capped and decode, which didn't know ISO 8859-9 (Latin-5,
the encoding of older tr_TR dictionaries) and refused it as unsupported.
decode now reads `SET ISO8859-9`, and a test writes an ISO 8859-9 .aff/.dic
to disk and finds words with ğ ı ş İ ç ö ü, as stems and with suffixes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
Audit finding 20.
- tr::suggest_with refuses words over 64 letters (and words with spaces), like the other
languages' suggesters (40 there; Turkish agglutinated words run past 40). Each candidate edit
copies the whole word, so a long word cost O(n^2) per suggestion request on the UI thread.
- TrDict::contains returns false past MAX_WORD_BYTES (120, the affix engine's bound) instead of
collecting the char indices of the whole word.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Audit findings 10 (storytold/wordcraft#3657) and 15 (storytold/wordcraft#3692).
- ru::hyphen::points: the cuts come in increasing order, so a repeat can only be the last one;
compare with `out.last()` instead of `out.contains` per syllable (O(V^2) on a long Cyrillic
word, during layout).
- hyphenate_word_in: walk the sorted break points alongside the chars instead of searching the
point list for every char. Same output.
Test: russian_hyphenation_and_hyphenated_output_are_linear (8x the letters costs ~8x; the old
code measured 31x and 30x).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Audit finding 4, and the rest of storytold/wordcraft#3661.
- dict.rs: `Dictionary::from_bytes` inflates with `decompress_to_vec_with_limit` (64 MiB; the
bundled en-US one is ~1 MB) and caps the words it keeps (bytes and count): front coding lets a
2-byte line spell out a 255-byte word, so the inflated size alone doesn't bound storage.
- hunspell.rs: one `read_capped` reader — regular files only, size checked on the open handle and
the read itself bounded by `take(MAX_FILE + 1)`, so no TOCTOU between metadata and read and no
endless read from a FIFO or device.
- tr: the Turkish loader reads through `read_capped` and decodes with `hunspell::decode` (it
read both files whole, with no cap at all).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of storytold/wordcraft#3613 (audit findings 2 and 6).
- Checker: `suffixed` and `form_has_flag` OR the flags of a group's matching rules into one
mask and recurse once, not once per rule. Same answer (the walk is a disjunction over flags);
16k self-continuing empty-append rules: 6 s -> <1 ms per check (debug).
- Finding 2: `forms` expands each (form, continuation) pair once, walks continuation rules through
a per-flag rule list, and stops at MAX_FORMS (4096) forms / MAX_FORMS_WORK rule visits, so a
dictionary of self-continuing rules can't allocate R^2 strings per stem.
- Finding 6: the suffix index finds a rule's (append, strip) group by hash, not by scanning the
append's groups: building with many strips per append is linear.
Not done here (rest of storytold/wordcraft#3613): a work budget for distinct-strip chains in the checker, rule caps
in parse_aff, proofing off the UI thread.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The close button was a ✕ (U+2715) text glyph and the version pill used →, neither of which the bundled faces are guaranteed to have; apps that panic on a missing glyph took the whole prompt down. The close mark is two painted strokes, the pill reads 'v0.1.0 to 0.2.0', and a test asserts the prompt paints only ASCII text.
Fixes#343
- FontStore::remove_files only touches names the family's manifest entry
lists and that are safe file names.
- A user source's folder carries a hash of its full ID, so sources whose
IDs slug alike don't share a folder.
- The download dialog and the sidebar note name the family's own source,
and the server for a source the user added.
- The face cache takes out only faces the last frame didn't draw.
- The catalog test lists missing translations instead of failing.
- ATTRIBUTION lists the three bundled craft-font-index files.
A group dragged to the window edge where an iconic column sits showed the
edge line and an outline around the column. The edge line is drawn just
inside the window, so it lay in the column's body, and paint_drop_line
outlines the iconic column a line lies in. That outline is for drops into
the column; a window-edge drop docks beside it, so it now shows the line
alone.