Commit Graph
1933 Commits
Author SHA1 Message Date
JeremyandClaude Opus 5.5 eff2500447 craft-update: migration review fixes (profile links, recovery message, Linux test)
Review follow-up (CodeRabbit and CI on #423):
- The profile roots and LOCALAPPDATA are canonicalised like the exe's folder, so a profile behind
  a junction or a subst drive still matches its default folder and still counts as the profile.
- When a failed move leaves no program to start, the helper starts nothing, so the recorded
  "run its installer" message is not replaced by a restart error.
- The default-folder test builds its expected path the way the plan does, so it also passes
  where Path::join uses '/' (Linux CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:33:30 +00:00
JeremyandClaude Opus 5.5 7fd60c5870 craft-update: a build from source is never offered the release it already contains
Reported by FredApps: PhotoCraft built from main as 0.17.0-integration.20261029.f5b3f123 offered
"Update and Restart" to 0.17.0, a downgrade. Semver puts a pre-release before its release, and
the update check and the self-update guard compared with plain semver.

New version::offers_update: a pre-release label the suite publishes (alpha, beta, rc, nightly)
sorts as before; any other label marks a build from source, which is only offered a strictly
higher X.Y.Z. check_for_update (through a new testable offered_release) and the self-update
guard both use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:29:47 +00:00
JeremyandClaude Opus 5.5 2e2e8f8daa craft-update: an app installed for one account moves to an install for everyone on its update
Temporary (craft-libs#422, step 4): new msi_migrate module, to be removed two releases after the
app packages become per-machine only.

The released packages refuse a per-machine install while a per-user copy exists, so the move
removes first and installs second: remove the per-user product (no permission prompt; if that
fails nothing has changed), install the new version for everyone, and if that fails or
permission is refused, install the new version for the account again in its old folder.
Settings and documents live outside the program folder and are never touched.

Which copies move: a per-user default folder goes to Program Files\ArtCraft\<App>; a chosen
folder outside the profile moves in place; a chosen folder inside the profile keeps updating
for the account. The launcher's own package never moves.

Tested with real Windows Installer runs of a renamed dual-scope package carrying the apps' own
install-policy.wxi: the move with the permission prompt accepted (twice), and with it refused
(the app came back for the account at the new version, the failed log kept). Also unit tests for
every plan and the order of steps, and a mutation check (9 of 9 killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:14:18 +00:00
JeremyandClaude Opus 5.5 cd7cc8eb39 craft-update: when the Windows Installer lookup cannot run, the uninstall records decide
Review follow-up (CodeRabbit on #419). The fallback trusted the App Paths hive, so a stale entry
or a product registered in both scopes could run the update in the wrong context. The fallback
now follows the launcher's rule: the context every uninstall record of the product agrees on
(msi_context::single_scope over HKLM in both views and HKCU). Missing or mixed records stop the
update, and an agreed context that differs from the App Paths hive still stops it as another
copy's registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:51:39 +00:00
JeremyandClaude Opus 5.5 1e01964871 craft-update: an app in the old per-user folder is judged by its registration first
Review follow-up (CodeRabbit on #419). The folder rule for the launcher's per-user package also
matched an app installed in %LOCALAPPDATA%\Programs\<App>, and it ran before the App Paths
check, so such a copy skipped the product-count, conflict and context checks. Now an app is
judged by its registration first; the folder rule only covers an app copy no registration names.

The launcher in its own folder still keeps its old path whatever is registered: on a real PC it
has an App Paths entry and a stale second uninstall record, and registration-first would have
stopped its self-update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:36:08 +00:00
JeremyandClaude Opus 5.5 7898779169 craft-update: a copy only takes the Windows Installer context of its own hive
Review follow-up (CodeRabbit on #419). The product code for the context lookup came from the
app's only uninstall record, which was not proven to own the running exe. A package writes App
Paths under HKMU, always the hive of its own context, so a registration whose Windows Installer
context differs from the hive that named this exe belongs to another copy. The update now stops
then, instead of taking the other copy's context.

Also documents the remaining helpers the diff touches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:21:27 +00:00
JeremyandClaude Opus 5.5 9ce610bf16 craft-update: a Windows MSI copy updates itself in its own context, wherever it is installed
Self-update only recognised an MSI copy by the launcher's per-user folder (the HKCU InstallDir
key, or %LOCALAPPDATA%\Programs\<App>). Every app installed for everyone, in
Program Files\ArtCraft\<App> or the older Program Files\<App>, was treated as a loose folder and
updated by unpacking the portable zip over Program Files without permission.

- msi_context moves here from the launcher (craft-launcher crates/native/src/msi_context.rs at
  b827fed, unchanged apart from visibility, MsiScope and scope_arguments). Windows Installer's
  own registration decides a copy's context, for the launcher and the apps alike, so the
  launcher can drop its copy and both routes follow one rule.
- New msi_copy module: a copy is an MSI copy when an App Paths entry names its exe (a registry
  read, cheap enough for status checks). Its update asks Windows Installer for the context; the
  App Paths hive is only the fallback when that lookup cannot run.
- The update stops, before the app exits, when the copy is registered in both hives, the lookup
  is unclear, or Windows lists the app more than once.
- After the update the helper starts the exe the registrations name now, since the package may
  have moved an exact old default folder.
- A failed self-update now names the Windows Installer log, as the launcher's installs do.
- The launcher's own per-user package is recognised and updated exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:08:01 +00:00
Dylan M. Taylor 8829439d68 crw: address the remaining CodeRabbit record findings
- 02 §4: the decoder refuses the G5 with Error::Unsupported; the embedded-JPEG fallback is the
  caller's, not the decoder's.
- 04: the Auto-slot claim holds outside the white-balance preset captures.
- The record README no longer says it holds everything in the order it happened: the derivation
  trail stays at the pinned LightCraft revision.
- docs/clean-room.md allows that split for a derivation done in another repository, so the
  standard and the CRW record agree.
2026-10-28 22:19:44 -04:00
Dylan M. Taylor b600e6a5cd crw: split decode_entropy under the cognitive-complexity limit
main's workspace lints now deny cognitive_complexity above 30, and decode_entropy scored 41.
The emit macro becomes a Chains struct, unstuffing its own function, and the three stream-end
checks one. Same algorithm: the pinned CC0 digests still match byte for byte.

Also correct two record lines: the G5 is the one file that does not match, and the frozen
code tables are not the only logs that ship.
2026-10-28 22:15:50 -04:00
Dylan M. Taylor fea36c4309 Merge branch 'main' into clean-room-crw-format-decode 2026-10-28 22:10:33 -04:00
aiongg 1a11a48978 craft-dialogs: native file and message dialogs that never block the event loop (#412)
* Add craft-dialogs: native file and message dialogs that never block the event loop

Requests queue and show at the end of the frame on the main thread; answers come
back with the app's continuation on a later frame. One dialog at a time, a drop
guard and a panic guard so a dialog never stays open, and an override for tests.

On macOS, Open and Folder show as a free-standing NSOpenPanel
(beginWithCompletionHandler, which rfd can't show), Save and message boxes as
sheets through rfd's async dialogs, never falling back to runModal. Filter
extensions match in any letter case on Linux.

* craft-dialogs: Dialogs::raise brings the open macOS panel to the front

* CI: dialogs workflow (Linux clippy and tests for craft-dialogs)

* craft-dialogs: per-instance macOS panel, parent window check, override panic guard, CI lints macOS code

* craft-dialogs: raise only a visible panel; Open and Folder need a running app
2026-10-28 16:49:37 -04:00
solarmaster961andCursor bd9a4d7b16 fix(linux): self-update a system-wide Flatpak bundle in place
A system install was offered only as a download. Reinstalling that bundle
through Flatpak's install-bundle action is denied without a password dialog
when polkit cannot fill in $(path). Use pkexec on the host instead, and keep
per-user bundles on flatpak install --user.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-28 09:11:52 +01:00
aiongg b4afa6d22a Merge pull request #307 from rbulboaca/fix/windows-print-vendor-ticket-prefix
print: give the job ticket the driver's own prefix for its vendor nam…
2026-10-28 00:47:44 -04:00
aiongg 5f00550cf7 print: respell a QName value written after whitespace
A pretty-printed ticket puts whitespace before the value, so the prefix
starts after it; without this the ticket fell back to Windows' spelling.
2026-10-28 04:28:55 +00:00
enriquemelzer-bitandenriquemelzer-bit e11f9d9aeb fix(menu): preserve Cargo 1.95 manifest compatibility (#366)
Co-authored-by: enriquemelzer-bit <319226743+enriquemelzer-bit@users.noreply.github.com>
2026-10-27 21:53:44 -04:00
Dylan M. Taylor 34f31b9a4c Refactor proof for strict Clippy checks 2026-10-28 01:06:48 +00:00
aiongg b380d16304 ci: run macOS only when a workflow is started by hand (#399)
* ci: window and ui run macOS only when started by hand

macOS runners are kept for releases. Pushes and pull requests run Linux and Windows; workflow_dispatch runs all three.

* ci: the other workflows run macOS only when started by hand

atomic, docpath, ai-core, i18n, print, pdf, update and keymap follow window and ui: Linux and Windows on pushes and pull requests (keymap: Windows), all their platforms on workflow_dispatch.
2026-10-27 15:18:59 -04:00
Dylan M. Taylor 6a66b95cbc Enforce Rust coding standards via Clippy (#372)
* chore(lints): enforce workspace Clippy standards

Deny cognitive_complexity (threshold 30, rust:S3776), too_many_arguments and wildcard_imports workspace-wide. Existing violations are fixed in follow-up commits.

* refactor(keymap): isolate shortcut row rendering

* test(ui): cover dock command contract cases

* fix(keymap): remove unused shortcut layout state

* refactor(media): simplify container serialization tests

* docs: document workspace lint inheritance for new crates

* refactor(text): split Knuth-Plass line costing and path extraction

Brings knuth_plass under the workspace cognitive complexity limit.

* test(font-store): split lookup test by concern
2026-10-27 13:49:38 -04:00
VoiceLessQ 61dbb4bf69 Merge remote-tracking branch 'upstream/main' into fix/proof-shared-language-engine
# Conflicts:
#	crates/proof/src/es/mod.rs
#	crates/proof/src/tr/mod.rs
2026-10-27 15:36:17 -02:00
aiongg d40c2be47d craft-ui: combo footer remeasures reset once the footer is stable (#394)
* craft-ui: combo footer remeasures reset once the footer is stable

* craft-ui: test combo footer recovery after the remeasure cap
2026-10-27 10:26:11 -04:00
Skaywalker 1d2fa3c66d proof: U+0085 and U+00A0 are flags in one-flag directives too (storytold/wordcraft#735) 2026-10-27 14:21:39 +01:00
aiongg 92d392cff1 craft-ui: ComboBox, egui's combo box with a menu that grows back (#387)
* craft-ui: combo boxes whose menu grows back with its contents

egui's ComboBox lays its menu out in the height it had on the previous pass,
and its scroll area takes no more than that. A filtered list that shrinks (a
search with no matches) never grows back when the search is cleared
(emilk/egui#5225, #5138). craft_ui::combo::menu offers its contents the full
height on every pass; craft_ui::combo::combo draws the combo box button
around it. The Font Manager's Language, Script and Sample menus use it.

* craft-ui: combo and menu take the close behaviour

A menu with a search field that closes itself on a pick (DesignCraft's font
menu) closes on a click outside only.

* craft-ui: ComboBox, a drop-in for egui's combo box, with header menus

craft_ui::ComboBox has egui 0.37's ComboBox builder and button (same
atoms, style classes and icon, pixel-identical in a test) with a menu that
grows back. show_ui_with_header and show_rows_with_header keep a search
field in place and scroll only the list; combo::Menu is the same menu under
a field an app draws itself. clippy.toml disallows egui::ComboBox.

The Font Manager's Language menu, the keymap dialog's combo boxes and the
print dialog's printer and duplex choices use it.

* craft-ui: combo menus keep to the room on their side, and footers

A menu is never taller than the room on the side of its field it opens on,
so an unbounded height fits on screen and scrolls. The side is settled once
the menu is measured and kept while it is open, so a menu that shrinks or
grows while typing doesn't jump to the other side of its field.

Footer forms keep a row under the scrolling list. Menu::new takes the
field's Ui for its default height and frame, and Menu::open shows the menu
under a text field that keeps the focus while typing.

* craft-ui: combo menus open by the app keep their side, and take picks

Menu::open takes &mut bool: the menu clears it when it closes (close
behavior, Escape, ui.close(), the field losing the focus to anything but
the menu), so a click on a row under a focused text field is picked.

The side settles only once the menu was shown and measured, also for
menus the app opens (Popup::open_id). A footer that changes height
remeasures the menu before it is shown. The popup frame's margins come
from the popup style; only above/below alignments are tried.

* craft-ui: a resting pointer over a combo menu doesn't keep it open

The menu counts the pointer as having moved into it only on a press or
click. A footer height change also requests a repaint, in case the
discard is denied.

* craft-ui: a footer menu opens without a second sizing pass

A footer height change no longer discards the pass or forces another
sizing pass: the menu measures again on the next pass and settles its
side then. A forced sizing pass ran a search field's focus request in a
pass where the field can't take the focus, so a footer menu opened with
the search unfocused.

* craft-ui: tests use Ui::scope_id

* craft-ui: hide the measuring pass of a footer menu, cap remeasures

The pass that measures a menu before its side is settled is laid out but
drawn fully transparent, so no frame shows a side or height the menu then
leaves. A footer that changes height on every pass remeasures twice per
open, then settles.

* craft-ui: tests satisfy clippy
2026-10-27 09:21:01 -04:00
Skaywalker 825c3da3c5 proof: numeric and long flags keep a UTF-8 .aff as text (storytold/wordcraft#735) 2026-10-27 13:09:33 +01:00
Skaywalker 33dc6ff6f2 proof: an AF count below 1 rejects the table, flags stay literal (storytold/wordcraft#735) 2026-10-27 13:09:33 +01:00
Skaywalker b56b2b0654 proof: .dic lines and alias numbers read as Hunspell reads them (storytold/wordcraft#735)
Checked against the Hunspell 1.7 command line:
- Without a tab or a morphological field the whole line is the entry, so
  a phrase with single-byte flags (tök jó/396 in hu_HU) no longer fails
  the load or loses its alias; leading blanks belong to the entry.
- With an AF table a flag field is the alias its leading digits number
  (fal/2x is alias 2, lab/b has none); an undefined alias, in a .dic
  entry or a continuation class, reads as no flags instead of failing
  the dictionary; a table without its count line is ignored.
- A REP count line may carry a comment, like AF and ICONV.

normalize copies the input only from its first change on, and SET and
FLAG are found in one pass over the .aff.
2026-10-27 13:09:33 +01:00
Skaywalker 244907cbf4 proof: read count comments, phrase entries and undefined aliases as Hunspell does (storytold/wordcraft#735)
Checked against the Hunspell 1.7 command line:
- The first AF and ICONV line is the count even with a trailing comment
  (AF 2 # aliases), so alias numbers no longer shift and ICONV 1 # no
  longer turns 1 into #.
- A .dic entry ends where Hunspell ends it (dic_entry_end), so ad hoc/B
  is one entry and ad alone is no word.
- An undefined alias in a .dic entry reads as no flags: the stem loads
  without affixes instead of failing the whole dictionary.
- IGNORE characters are removed from the appended text only; a stripped
  text that holds one never matches a stem, as in Hunspell.
- FLAG UTF-8 and SET are found anywhere in the .aff, not only in the
  first 8 KB.

Also: normalize borrows the input when no conversion or ignored character
applies, the UTF-8 .aff reader counts fields as it goes and builds its
error context only on error, and the single-byte flag reader is
decode_latin1.
2026-10-27 13:09:33 +01:00
Skaywalker 1e2a81bb93 proof: SUBSTANDARD stems are correct but never suggested, as with NOSUGGEST 2026-10-27 13:09:33 +01:00
Skaywalker 2d10f23bf4 proof: test that unsupported directives are refused and morphology-only flags load
COMPOUNDFIRST and COMPOUNDLAST, the older names of COMPOUNDBEGIN and
COMPOUNDEND, are refused like them until compounding is supported.
2026-10-27 13:09:33 +01:00
Skaywalker e21a078b5a proof: a one-flag field keeps its first byte, a directive reads only its first argument
Without FLAG UTF-8, Hunspell reads a field that names one flag (PFX/SFX,
KEEPCASE, FORBIDDENWORD and the like) through decode_flag, which takes its
first byte: SFX é is the flag 0xC3. Such a field no longer fails the load.
SYLLABLENUM lists flags and is read byte by byte like AF. SET and FLAG
read only their first argument, so FLAG UTF-8 with a trailing comment
keeps Unicode flags.
2026-10-27 13:09:33 +01:00
Skaywalker 0f961dbc58 proof: .aff comments start after a directive's fields, .dic entries end where Hunspell ends them 2026-10-27 13:09:33 +01:00
Skaywalker 4e20a2d871 proof: flag aliases, single-byte flags, ICONV, IGNORE and KEEPCASE in affix dictionaries (storytold/wordcraft#735)
The Hungarian Hunspell dictionary declares SET UTF-8 but writes its flags as single bytes, uses AF aliases and 158 distinct flags. Read flag fields byte by byte unless FLAG UTF-8 is set, expand AF aliases, skip AM, apply ICONV and IGNORE, honour KEEPCASE, and widen flag masks to 256 bits. Compounding and NEEDAFFIX stay unsupported for now.
2026-10-27 13:09:33 +01:00
TomcattandClaude Opus 5.5 04950b5a63 fix(update): a standalone self-update replaces the whole tarball install
A standalone copy installed from the release tarball (`bin/` and `share/` unpacked into a prefix
such as ~/.local) only had `bin/<app>` swapped, so after an update the CLI beside it and the app's
data, desktop entry and icons stayed on the old release.

The tarball is now unpacked with `install::tree::unpack` (bounded, contained), and besides the
executable the update replaces:
- the release's other programs that are already in the executable's folder;
- everything under the tarball's `share`, when the executable sits in a prefix's `bin` that has the
  app's `share/<id>` folder.

A copy on its own in some folder still gets only its executable. Each file is staged next to its
destination and renamed over it, without following links, with group/world write and set-id bits
dropped; the executable goes last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oRr8pRoNvNUzhf8qyiEdm
2026-10-27 20:20:23 +09:00
aiongg bd97cb760c craft-ui: iconic flyouts stay open by default (#384) 2026-10-27 01:49:01 -04:00
Dylan M. Taylor df10ce00a5 crw: fix the low-bits discriminator on payloads that end in the trailer
A no-plane PowerShot frame whose payload is shorter than plane + header ends
inside the scan window with the record's real `ff d9 00` trailer, so rule (a)
read the `ff d9` as a plane byte, reported 12 bits and then refused a valid
file. Both discriminator signs now require a payload long enough to hold a
plane, so a flat G1 frame decodes instead of being rejected.

Also from the review: clamp samples to the declared depth so a damaged stream
cannot put more than `bits` into a consumer's `1 << bits` LUT, report zero
sensor dimensions as corrupt rather than as a limit, take a signed ImageInfo
rotation modulo 360 (so -90 is 270), warn against pairing BlackLevel::values
with Header::cfa by index, and pin actions/cache in the crate's workflow.

Tests cover the trailer payload, end-of-block chain values, the clamp, zero
dimensions, both white-balance slot layouts with every Canon setting, and each
rotation spelling. The 11 CC0 samples still decode exact with
CRAFT_CRW_REQUIRE_SAMPLES=1.
2026-10-26 18:10:03 -04:00
Dylan M. Taylor 903b816bdf crw: keep the format record, drop the derivation trail
The crate's record should hold what the decoder is built from, not the process
that produced it. The starting assumptions, the plan, the container notes, the
entropy-derivation log, the chart calibration, the probe scripts and their
observational logs are not part of it. The frozen-table JSONs and
tools/gen_tables.py stay, so src/tables.rs can still be regenerated and audited.

Renumber the surviving documents 01-05 in reading order and update every
reference to them, so the record reads in order and cites nothing that is no
longer there.

AGENTS.md and CLAUDE.md go back to main: repository instructions are not part of
this change.
2026-10-26 17:40:25 -04:00
Dylan M. Taylor 901f8cad59 Sonar: reduce cognitive complexity in highlight, review and outline commands 2026-10-26 16:01:36 -04:00
Jürg Lehni d201bc1964 docking: a native floating window's bar shows no title, as its tab names the panel 2026-10-26 20:57:27 +01:00
RWoldberg 536fa73911 Remove line 2026-10-26 10:28:38 -06:00
RWoldberg 70decab207 Test update 2026-10-26 10:24:43 -06:00
RWoldberg db0fe2045c Review fixes 2026-10-26 09:53:53 -06:00
RWoldbergandClaude Fable 5.1 4a4863fd94 craft-crw: cite the LightCraft branch tip in the provenance notes
The branch gained review commits after the port (chrono for a date conversion that this
crate does not contain, a test skip macro); the decoder and the clean-room record are the
same at both revisions, so the notes name the tip and the ported revision.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-26 08:21:27 -06:00
RWoldbergandClaude Fable 5.1 a0c8a08020 craft-crw: Canon CRW (CIFF) raw decoder, moved from LightCraft with its clean-room record
The clean-room Canon CRW decoder written in LightCraft (crates/raw/src/vendor/crw.rs,
crw_tables.rs, branch clean-room-crw-format-decode, revision 70c755e4) becomes the shared
crate craft-crw: the CIFF heap container, the entropy coding derived by black-box analysis
(frozen code tables for selectors 0-2), sensor black and white levels, as-shot white
balance, orientation and the make/model/time records. Public types are the crate's own
(Error, Limits, Cfa, Rect, BlackLevel, Header, Image); the capture time is handed on as
Unix seconds so no calendar code lives here; LightCraft's shared plateau estimate is
copied as saturation_plateau. Algorithm, tables and thresholds are unchanged.

Tests: the ported synthetic round-trip, table-structure and hostile-input tests; a
proptest mutation sweep (tests/robust.rs); and tests/samples.rs, which pins the eleven
CC0 raw.pixls.us sample files (tests/samples.json: URL, SHA-256, licence) to the SHA-256
of the verified decode plus levels, white balance and metadata, so the port is proven
byte-identical to the LightCraft decode without redistributing the reference TIFFs.
scripts/fetch-crw-samples.py downloads the files outside the checkout; CRAFT_CRW_SAMPLES
and CRAFT_CRW_ORACLE point the tests at them. The crate builds for wasm32.

The whole derivation record (starting assumptions, plan with the maintainer's decisions,
source log, container and payload notes, derivation log, frozen format, verification,
colour/levels notes, private-corpus and chart notes, scripts, logs and the frozen table
JSON) moves verbatim to crates/crw/clean-room/ with a README that indexes it and maps the
LightCraft paths it cites; tools/gen_tables.py regenerates src/tables.rs from the JSON,
byte for byte.

Repository: .github/workflows/crw.yml (fmt, clippy, tests on Linux, Windows and macOS;
wasm check; the sample-file job with cached downloads), README and ATTRIBUTION rows,
NOTICE source provenance, docs/clean-room.md (the clean-room standard a format crate
follows here), and AGENTS.md expanded with the start-of-session steps, the new-crate
checklist, clean-room, assets and test rules; CLAUDE.md points at it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-26 08:20:01 -06:00
skylar taylor-barrick 9ff719499d Cover ZIP drive-prefix rejection and standalone update helper ownership (#335)
* test(update): cover ZIP drive prefixes and standalone helper ownership

* test(update): synchronize standalone launcher replacement regression

* test(update): assert standalone updates select portable data paths

* Supply pinned fonts throughout instrumented coverage

* Make development-build regression independent of test runner location

* Pin coverage workflow with immutable nested actions

* Use reviewed optional-font coverage setup

* ci: restore craft-common-actions tracking main
2026-10-25 23:41:12 -04:00
aiongg b926b5c03e Merge pull request #355 from aiongg/docking-native-ime
docking: native floating windows publish their IME descriptor
2026-10-25 23:26:45 -04:00
aiongg 39d134fb54 keymap ui: Assign takes a held shortcut in one step (#352)
Assign in the detail pane adds the recorded chord to the command and takes
it from the commands listed under "Currently assigned to", as one undo
step, without opening the conflict bar. ConflictMode::Keep keeps both.
A chord recorded in a row's cell still asks with the conflict bar.
2026-10-25 23:25:35 -04:00
aiongg b74f1b6ba4 Merge pull request #340 from aiongg/lang-4-window
craft-window: window list, cascade placement, Window menu and viewports for apps with one window per document
2026-10-25 23:24:56 -04:00
aiongg fda40f0ed8 Merge pull request #339 from aiongg/lang-3-fonts
craft-font-catalog, craft-font-store and craft-font-manager: installed fonts, the font store and the Font Manager panel
2026-10-25 23:23:15 -04:00
aiongg e3e3c4ab5f Merge pull request #338 from aiongg/lang-2-ui
craft-ui: interface fonts, text direction, IME composer, localized widgets and screen testing
2026-10-25 23:22:35 -04:00
aiongg 4db3dd0117 Merge commit '819fd27f002439df60c2892715bfa322b2e566ea' into docking-native-ime 2026-10-26 03:15:20 +00:00
aiongg 819fd27f00 Merge branch 'lang-3-fonts' into lang-4-window 2026-10-26 03:14:38 +00:00