2856 Commits
Author SHA1 Message Date
aiongg 360150dcd4 Merge pull request #7281 from wanikhawar/claude/blur-tool-lag-684245
perf(retouch): releasing a Blur/Smudge/Dodge stroke takes its live pixels
2026-10-24 11:13:32 -04:00
David Mann 2b0deddec2 Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/ui-egui/src/panels.rs
2026-10-24 00:51:37 +02:00
Max WittandBrandon Thomas ed0286d324 New Document: category tabs and preset cards show a pointer, not a text cursor (#3647)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:37:08 -04:00
Vikram BhaskaranandBrandon Thomas 117faadb79 Isolate live distortion tools per document (#5115) (#5447)
* Drop live distortion tools when switching or closing documents.

Puppet Warp, Liquify, and the other modal distort tools kept session UI on a global prompt, so undo, delete, and pixels leaked across tabs. Cancel the live tool with the document that owns it.

* Fix Liquify isolation test: new documents have nothing to undo.

file.new leaves an empty history, so the second document needs a layer step before edit.undo can prove Ctrl+Z is not swallowed.

* Stop expecting Puppet Warp's preview to follow a new document.

display_doc is keyed to the session's document (#5115), so a File › New
tab no longer shows the old warp. drop_foreign still closes the session.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:35:47 -04:00
Lucas RodriguesandBrandon Thomas 15db2d8560 Paste: keep the pasted image on the canvas (#6759) (#6798)
* Paste: keep the pasted image on the canvas (#6759)

Paste centred the clipboard image on the view centre with no clamp, so an
image that fit the canvas could land partly off it, and one larger than
the canvas could leave blank edges. Now, per axis, an image that fits stays
wholly inside the canvas, as close to the view centre as it can, and one
larger than the canvas starts at the canvas's top-left edge, as in
Photoshop. Paste Into / Outside still centre on the selection
("clampToCanvas": false). Centres are computed edge by edge in f64, so
extreme coordinates no longer overflow.

* Paste: doc comments for Paste Into and the paste placement tests

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:35:21 -04:00
Hoddcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>Claude Opus 5.5Brandon Thomas
d878e38333 Color pickers: pick on press and follow small drags (#7793) (#7822)
* Color pickers: pick on press and follow small drags (#7793)

The Color Picker dialog's field and slider, and the Color panel's
field and hue strip, only applied the colour on dragged()/clicked().
egui holds dragged() back until the pointer moves past its click
threshold (~6 pt) or is held a while, so a press picked nothing at
first and small adjustments jumped.

Add widgets::pick_pos, which reads the pointer from the frame the
button goes down (is_pointer_button_down_on), and use it on all four
surfaces. The Color panel now restyles selected type only when the
picked colour changes.

Tests: color_picker_ui::field_and_slider_pick_on_press_and_follow_small_moves,
panels::color_tests::color_panel_field_and_hue_strip_pick_on_press.

* #1 Reverted accidental scoreboard change when regenerating it
#2 Fixed color picker not checking for pointer press, but any mouse click

* Update crates/ui-egui/src/widgets.rs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Color pickers: rustfmt pick_pos

* test(ui): use main's rgb_to_hsb in the colour picker tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:34:41 -04:00
f719f0bfac Perspective Crop: the swap button swaps W and H (#7059) (#7060)
* Perspective Crop: the swap button swaps W and H (#7059)

The W/H swap button called crop_size::swap, which only acts in the Crop tool's W x H x Resolution mode; Perspective Crop never sets that mode, so the button did nothing there. The button now always swaps the fields; X keeps the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: the test clicks Perspective Crop's ⇄ button

The test now renders the real options bar with the Perspective Crop tool,
clicks ⇄ through the egui_kittest harness and checks that W and H swapped,
instead of calling `swap_fields` directly. It fails if the button goes back
to calling `swap`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:34:16 -04:00
David Mann 9c5a7b907a Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/ui-egui/src/i18n/tr.tsv
#	docs/ui-parity.md
2026-10-24 00:34:11 +02:00
427fb34042 Curves: the graph grows with the Properties panel and a resizable dialog (#8984) (#8998)
* Curves: the graph grows with the Properties panel and a resizable dialog (#8984)

The graph was fixed at 300 points. It now takes the width it is given,
no taller than the window leaves room for beside the other rows. The
Image > Adjustments > Curves dialog gets a resize grip and reopens at
the size it was left at.

* Curves: the dialog stops widening where the graph stops; a squeezed size isn't remembered (#8984)

The dialog's resize grip goes no wider than the graph can grow in this
window, so no blank column. A size at the window's limit no longer
replaces the size the user left the dialog at.

* test(ui): keep the Issue #43 doc comment on its own test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:34:02 -04:00
David Mann 8c04b175ec Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/automation/src/rpc.rs
#	crates/automation/src/server.rs
#	crates/gpu/src/health.rs
#	crates/text/src/tests.rs
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/panels.rs
#	docs/control-protocol.md
#	docs/ui-parity.md
2026-10-24 00:23:15 +02:00
Marco ArnulfoandBrandon Thomas 2b83be6c81 Levels, Curves and the other adjustments: ⌥ plus the shortcut opens the dialog at its last settings (#8900) (#8969)
* Levels, Curves and the other adjustments: ⌥ plus the shortcut opens the dialog at its last settings (#8900)

* Image › Adjustments: ⌥ while choosing an item opens it at its last settings (#8900)

* Tests: the ⌥-click test picks the Image menu's Adjustments row, not the panel tab (#8900)

* Image › Adjustments: an item opened with ⌥ lands in Edit › Search's recents too (#8900)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 17:55:18 -04:00
Batuhan GözegüandBrandon Thomas 9fe6be6d86 i18n(tr): fix mistranslations, inconsistencies and untranslated rows (#8871)
- Color Decontaminate: "Renkli" (colourful) -> "Renkleri Arındır"
- Warp split commands and "Cancel warp": "Çözgü" (textile warp) -> "Çarpıtma"
- Constraint hint: "Shift" was translated as "kaydırma"; keep the key name
- Highlights (tonal range) -> "Parlak Tonlar", as in the other rows
- Luminance -> "Lüminans" (was "parlaklık", which is Brightness)
- Indexed -> "İndekslenmiş", Trilinear -> "Üç Doğrusal"
- Levels In/Out Black/White: distinct "Giriş/Çıkış Siyahı/Beyazı"
- Layer Knocks Out Drop Shadow now says "Alt Gölge" like Drop Shadow
- Contact Sheet II -> "Kontak Sayfası II"
- Fix word order of the clone-source hints, "After (X)", "On Black/White"
- Translate "Preview size" and "Print preview image"
- Keep theme names Solarized and Breeze; Pro Light -> "Pro Açık"
- Add contributor entry for the author

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 17:54:52 -04:00
Mathieu Rancourt 5004d96acb Lost window device: quitting goes through while recovery can't be saved (#8620)
After the window's GPU device is lost, nothing can be drawn (#6464). When
saving the unsaved work for recovery keeps failing (a full disk, an
unwritable or missing recovery folder), the app keeps retrying. Quitting
(the title bar's close button, File › Exit, the window manager's close)
then parked the quit behind the unsaved-changes prompt, which can never
show, so the blank window could not be closed. discard_ui::intercept now
lets a quit through once the device is lost, with a warning in the log.
2026-10-23 17:41:31 -04:00
Mathieu Rancourt 5de94a7e05 Slice tool: Style offers Fixed Ratio and Fixed Size (#8360) (#8638)
* Slice tool: Style offers Fixed Ratio and Fixed Size (#8360)

The Slice tool's options bar showed "Style: Normal" as plain text, so a
slice could only be drawn freely. It now has the marquee's Style dropdown
(Normal, Fixed Ratio, Fixed Size) with Width and Height, and a new slice
follows it while it is drawn and when it is made, through the marquee's
chrome_ui::marquee_end. The options are kept with the other tool options,
checked when loaded, and reset by Reset Tool.

* Slice tool: a Fixed Size slice has exactly its size, also a pixel or two

A press inside a pixel (10.25) made a 64 x 64 Fixed Size slice 65 x 65
pixels, since its far edge was rounded out. Fixed Size now starts on the
pixel the press is in. A Fixed Size of 1 or 2 pixels was taken for a click
that selects; Fixed Size makes its slice at any size.

* Slice tool: Fixed Ratio keeps its ratio and Fixed Size whole pixels

A fractional drag rounded each edge of a Fixed Ratio slice apart, so 2 : 1
could become 5 x 3. A fixed style now starts on the pixel the press is in
and takes both sides from one scale: a whole-number ratio comes in whole
multiples of it in its lowest terms, any other as close as whole pixels
allow. Fixed Size rounds its Width and Height to whole pixels, in the
field and when the slice is made, so 1.5 px no longer grows to 2.

* Slice tool: Fixed Ratio follows the drag, Fixed Size fields in whole px

Whole multiples of the ratio grew a 1366 : 768 slice in steps of 683 px,
made a 4 x 1 slice from a plain click under 4 : 1, and jumped a 39.8 px
16 : 9 drag to 48 x 27. The longer side now follows the drag to within a
pixel and the shorter side takes the ratio from it. Fixed Size Width and
Height show whole pixels, so the field matches the slice it makes.
2026-10-23 17:41:25 -04:00
FinnNkandClaude Opus 5.5 2cd1bd8664 fix(ui): cap text fields and oversized text events (#8722)
* fix(ui): cap text fields and oversized text events

Plain text fields took any amount of text, so pasting a huge string grew
the string (and its laid-out galley) without bound; #2258 fixed this for
numeric fields only. The shared text_field now holds at most 1024
characters unless its caller sets a limit, and the app cuts every text,
paste and IME event to 1 MiB of characters (at a character boundary)
before any widget sees it, so the dozens of hand-built TextEdits are
bounded too.

Part of #2150

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): bound oversized existing text and synthetic text events

A text_field whose value is already past its limit lays out a bounded
copy and writes it back only on an edit; the text-event cap runs after
the control channel's synthetic events join the input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ui): raw_input_hook caps text the control channel queues

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:41:14 -04:00
MarekSpodBiedry b491de8e29 fix(layers): keep blend mode, Opacity, locks and Fill visible but greyed with no layer selected (#8880) (#8904) 2026-10-23 17:40:51 -04:00
paulou-jpgandClaude Opus 5.5 0bd37f81af Puppet Warp pins go where they are dragged, without snapping (#8217)
A pin drag took the active tool's snapping. With the Move tool that was a
layer move's Smart Guides, which View > Snap doesn't turn off, so pins jumped
to other layers' edges with snapping off. Puppet Warp and Perspective Warp
drags no longer snap, as in Photoshop.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:40:39 -04:00
haiandzhanghaijun01 7264368a75 Layers: show effect row names through tl! (#8754)
The Layers panel drew Effect::label and "Effects" in English even when catalogs already translated them.

Co-authored-by: zhanghaijun01 <zhanghaijun01@bianfeng.com>
2026-10-23 17:39:55 -04:00
FinnNkandClaude Opus 5.5 c06dd9c27c fix(gpu): rebuild the GPU canvas after an out-of-memory error (#8774)
* fix(gpu): rebuild the GPU canvas after an out-of-memory error

An uncaptured wgpu error of any kind dropped the GPU canvas for the rest
of the session, so one recoverable Out of Memory on a working device
left every redraw on the CPU until Retry GPU. Dropping the canvas frees
its memory, so after an out-of-memory error a new GPU canvas is now
built at once on the same device, with a fresh health flag and no
dialog, up to three times a session; past that the CPU renderer and the
Retry GPU dialog stay as before. Validation errors and device losses
are unchanged.

Fixes #6564

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(gpu): only wgpu's out-of-memory error is retried

The fault is classified by wgpu's Error::OutOfMemory variant and recorded
as exactly "Out of Memory", so a validation error that mentions memory
keeps its fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(gpu): a later out-of-memory error replaces an earlier error

So the canvas still rebuilds itself when memory ran out after a
validation error in the same frame; a device loss still wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:39:50 -04:00
skylar taylor-barrickandskylartaylor 2d9640bb0b fix(ui): restore extras when showing the grid (#8962)
Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
2026-10-23 17:39:24 -04:00
41599c591c Crop default mode: moving the image under the box snaps (#8580) (#8622)
* Crop default mode: moving the image under the box snaps (#8580)

In the default (non-Classic) crop mode a drag inside the box moves the
image under it, but snapping was switched off for that drag, so content
couldn't be centred in a larger box without leaving for the Move tool.

The drag now snaps like a Classic Mode frame move: the frame moves by the
image's travel the other way over the document, its edges and centre snap
to the document, layers, guides and grid (smart guides when those are
empty), and the pointer is corrected in the press camera, as crop_ui reads
the drag. A turned box still doesn't snap.

Fixes #8580

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Crop image move: smart guides snap when no Snap To target is in reach (#8622 review)

The document's bounds keep the regular targets non-empty, so the smart
guides were never tried: with View › Snap To › Layers off, the box never
snapped to a layer while the image moved under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Khawar Zamman Wani <wanikhawar@Khawars-MacBook-Air.local>
2026-10-23 17:39:02 -04:00
Ignat Ai 0c124bf388 i18n(zh-hans): translate status messages and preview labels (#8872) 2026-10-23 17:38:13 -04:00
Kushagra Sharma 7e3594b822 fix(ui-egui): a click on the bottom layer's Smart Filters selects them again (#8764) (#8974)
* fix(ui-egui): a click on the bottom layer's Smart Filters selects them again (#8764)

The empty space below the last Layers panel row deselects every layer on a
click (#8100). It started under the last layer's own row, so it covered that
layer's effect and Smart Filter rows and, registered after them, took their
clicks: on the bottom smart object (a new document's Layer 0), clicking a
filter deselected everything instead of selecting it.

The click area now starts under everything drawn for the last layer. The
drop zone below it (#1721) is unchanged.

* Contributors: credit drunkeninja42 as Kushagra Sharma
2026-10-23 17:38:02 -04:00
aiongg 7aaa59712a Merge main into integration-batch-7 2026-10-23 20:37:05 +00:00
Dylan M. Taylor f3f2acc5ec Merge pull request #8960 from dylanmtaylor/security/gpu-plan-and-ui-swatch
security(gpu,ui-egui): bound clipped group planning; pattern swatch sampling
2026-10-23 15:52:58 -04:00
aiongg c3aa3f9e51 Tests: missing translations don't fail; xtask ci prints them
Delete the coverage tests, the per-language parity checks and the tests
of one language's exact wording. Catalog integrity, placeholder and
plural-form checks stay, as do the engine tests (locale resolution,
plural rules, fallback, language scoping, drawing in a language); those
compare against tr() or the plural rule rather than catalog wording.
Docs describe the report instead of a failing test.
2026-10-23 19:07:23 +00:00
Dylan M. Taylor b20ccefffb security(gpu,ui-egui): bound clipped pass-through planning; sample pattern swatch pixels directly 2026-10-23 14:41:54 -04:00
Dylan M. TaylorandCodebuff f27656b910 fix: restrict droplet shim permissions and annotate test range (SonarCloud) (#8870)
The generated droplet shim only needs to run as its creator, so 0700 keeps
other local accounts from reading the CLI path it invokes. The reversed
range in the arrow-key test is the case under test, not an accident.

🤖 Generated with Codebuff

Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-23 09:57:10 -04:00
aiongg ed1b9cff3f Merge pull request #8783 from wanikhawar/select-mask-edits
Select and Mask: tool edits as select.refineEdge params (engine, part 1 of 2)
2026-10-23 09:49:52 -04:00
Dylan M. TaylorandCodebuff 96042b8e7a fix(text): bound untrusted font and PSD text processing (#8768)
* fix(text): bound font paths and imported text processing

Reject encoded path tricks and cap resource-heavy font and PSD text processing to keep untrusted inputs bounded.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* fix(text): reject percent-encoded DEL in served paths; satisfy clippy chunks_exact lint

---------

Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-23 09:40:04 -04:00
Khawar Zamman WaniandClaude Opus 5.5 4970534e05 feat(engine): Select and Mask tool edits as select.refineEdge params
select.refineEdge takes `edits`: the Select and Mask workspace's Quick
Selection, Brush, Lasso, Object Selection, Select Subject, Clear Selection
and Invert, applied in order to the starting selection (or layer mask)
before the edge is refined. Cancel discards them, OK is one history step,
and agents drive them over MCP and the control channel.

- engine/select_mask_edits.rs: parse (bounded, every bad field an Err)
  and a pure apply/apply_one the workspace preview can share
- algo matting::brush_region: soft round brush coverage (hardness)
- ATN export reports steps with edits instead of dropping them
- "Edits" label in the 21 catalogs

Part of #7069 and #8148 (the workspace UI follows in a second PR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:30:46 +05:30
David MannandClaude Opus 5.5 7c4af5bdf1 Address CodeRabbit review: only this call's failed start opens the updates window (#5485)
b1b6bd248 opened the window whenever the status was Failed after an
error, which includes a Failed left from an earlier check, so a later call
refused for bad params opened it too. It now opens only when this call's
start failed: the status changed to Failed, or the error is the Failed
message (the same start failure again). The test covers bad params after a
failed check, bad params from Idle, and a repeated start failure.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:48:03 +02:00
David MannandClaude Opus 5.5 7d13c854a3 Address CodeRabbit review: the held Type press keeps the path pointer until it drags a box (#6230)
81bcf7f3d hid the path pointer for any drag, but every Type press starts
one, so pressing on a path showed the I-beam although a click or flat drag
still makes path type on release. While the button is held the pointer is
now what the release makes: nothing once the drag passes the box threshold,
else the path click at the press. pointer_up and the hover share the
threshold (makes_box). The test holds a press on a shape, drags flat (area
type on release) and drags a real box from inside the shape (no pointer).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:46:28 +02:00
David MannandClaude Opus 5.5 b1b6bd2481 Address CodeRabbit review: a manual update check that fails to start opens its window (#5485)
When the checker cannot start, help.checkForUpdates sets Status::Failed and
returns the error, and `?` skipped opening the window, so Help > Check for
Updates showed neither the failure nor Check Again. The window now opens
for a failed check (the command error is still returned); bad params open
nothing. Covered by a test with a checker that fails to start.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:31:11 +02:00
David MannandClaude Opus 5.5 81bcf7f3df Address CodeRabbit review: no path pointer while a text box is dragged out (#6230)
Dragging out a paragraph box with the Type tool sets app.drag and no type
edit is open yet, so the hover lookup showed the area-type or path-type
pointer over a path although the release makes a box. hover_path_target now
returns nothing during a drag or an open type edit (the edit check moved in
from canvas.rs), and a test drives a drag over a shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:30:36 +02:00
David Mann 1efa06b715 Integration: merge main (c36e46c38 #8680, #8668, #6705)
# Conflicts:
#	docs/ui-parity.md
2026-10-22 23:47:54 +02:00
David Mann 104fea5c1b Merge PR #6055: Color panel: Photoshop's panel-menu modes, the same in every theme (#6000) (author's update since review: main merge; batch resolution kept) 2026-10-22 23:41:06 +02:00
David Mann 450b832a4c Merge PR #6254: Selection: ⇧ mid-drag locks a selection-tool move to the axis (#6231) 2026-10-22 23:40:21 +02:00
David Mann ca73fb4724 Merge PR #6966: perf(canvas): skip occluded checker on CPU textures 2026-10-22 23:40:20 +02:00
David Mann 3623a8b6c8 Merge PR #7021: Eyedropper: a large sampling ring that reads on any colour (#6973) 2026-10-22 23:40:20 +02:00
David Mann d4a5be2980 Integration: #5878 third fix-up (a deliberate change in this instance beats another instance's migration) 2026-10-22 23:40:20 +02:00
David Mann 09eef59e90 Integration: #5878 second fix-up (two instances keep each other's Shift tool switch choice) 2026-10-22 23:40:18 +02:00
David Mann c436f61d53 Merge PR #7202: fix(atn): import a recorded Save As with its destination (#7102) 2026-10-22 23:40:18 +02:00
David Mann 9ee6c407b6 Merge PR #8254: Image Assets: optionally keep a transparent artboard's full size 2026-10-22 23:40:16 +02:00
David Mann a23c29b53a Merge PR #5878: Preferences: old settings files take Shift-to-switch-tools on (#5647) 2026-10-22 23:40:16 +02:00
David Mann 79ab013961 Merge PR #8496: fix(ui-egui): expand layer visibility eye hitbox and render sunken well (#8467) 2026-10-22 23:40:16 +02:00
David Mann 24051914fd Merge PR #6352: fix(engine): honor lock all on layer mask commands 2026-10-22 23:40:15 +02:00
David Mann 3fdcabded4 Merge PR #5248: Keep adjustment clipping visible in compact Properties panels 2026-10-22 23:40:15 +02:00
David Mann 49a98690ad Merge PR #8354: Keep the CPU canvas sharp at 100% zoom 2026-10-22 23:40:14 +02:00
David Mann 0fcdd660cb Merge PR #7849: Menu bar: a held Alt's auto-repeat doesn't re-arm the Alt tap 2026-10-22 23:40:12 +02:00