Plugin surfaces
Explore the Explorer and the Markdown, HTML, and image viewers. These tools sit alongside terminals in the work area, so you can read files and check results without leaving your workspace.
This page uses a saved copy of the design system, last changed on . It may differ from the current design source.
Explorer — the file-manager surface
Editable path field — shared address component (Explorer + Markdown)
Both the Explorer toolbar and the Markdown chrome now share one editable path field — a single design SoT for the “path address” role. Display: one mono path line (ellipsis, --tasty-text-secondary) behind a leading icon, with a trailing Go (arrow-right) button. Click to edit: focus ring + blink caret, text goes --tasty-text-primary; ↵ or Go navigate to the typed path, Esc reverts. While editing, a AutoComplete candidate dropdown hangs below the field — a typeahead of recent paths (Explorer = recent directories, Markdown = recent files) that narrows as you type; ↑↓ move the keyboard-active row, ↵/Go confirm. The only per-surface parameters are the leading icon slot (Explorer folderOpen / Markdown file) and the candidate list. Field tokens align to forms/Input: --tasty-input-bg / --tasty-input-border (idle) → --tasty-input-border-focus (edit) + focus ring.
Open questions resolved (design SoT): (1) leading icon is a per-surface slot — Explorer folderOpen, Markdown file — mirroring forms/Input's leading-icon slot. (2) Explorer's breadcrumb (Crumb + chevron) is dropped for the plain editable field; a hybrid crumb↔edit form was rejected. (3) Go button on both surfaces. (4) only the field + dropdown are shared; the outer bar stays per-surface. (5 — new) the edit-time candidate dropdown is the formal AutoComplete component (full state matrix — filtered / overflow-scroll / empty / hover vs keyboard-active — lives on the Components page), replacing the source-only Combobox. Source home: extract PathField into crates/tasty-ui-widgets composing the renamed AutoComplete (was Combobox); recent-path candidate source + wiring/i18n tracked separately from this design pass.
Explorer surface — full layout (Detail view)
A full file manager living in a work-area tile, like the terminal/markdown surfaces. Top→bottom: an internal directory tab bar (lighter than the pane tab strip — each tab is its own cwd), a toolbar (Back / Forward / Up / Refresh · editable path field · grid/list/detail view toggle), then a body of a 196px sidebar (Files tree over a Favorites list) feeding the content area. Default view is Detail; the address is an editable path field shared with the Markdown surface (click to type a path, ↵/Go to jump to that directory).
Defaults chosen (briefs left these open): Detail is the default view; the right-hand preview panel is dropped in favour of a wider content area (re-add later as a toggle if needed); no in-toolbar filter search in this pass; the tree is rooted at Home. Favorites are global across surfaces.
View toggle (SegToggle) — a segment, so it fills (settled)
The 2026-09-17 rule reads “underline switches a view, fill picks a value”. The Explorer view toggle looked like it sat on both sides: it is drawn as a fill, but what it changes is called ExplorerViewMode. It is a segment. “View” in the rule means different content — a tab strip swaps what is shown. grid / list / detail show the same listing drawn three ways; that is a setting, a value. So the selected segment takes the segment recipe already settled for the remote Connection toggle: --tasty-accent-primary fill + --tasty-text-on-accent ink — icon-only segments included, the glyph inherits the ink via currentColor. --tasty-surface-active leaves this control for good: it is row selection, never a segment fill. Off and hover are unchanged.
Rule, extended: underline switches content (tab strip); fill picks a value (segment) — including a value that only changes how the same content is drawn. --tasty-surface-active is row selection and belongs to neither. No third branch is needed; the word “view” in a variable name does not make a control a tab strip.
Don't draw a 2px underline inside the toggle container — the segments sit on the container's own border and an underline would collide with it; that vocabulary belongs to tab strips at the top of a surface.
Sidebar Favorites — populated vs. empty state
The sidebar Favorites section caption is always shown, even at zero favorites — so the feature never disappears and reads as unimplemented. With favorites it lists STAR rows (left); empty, the caption stays and an empty state fills the slot: a faint outline star + "No favorites yet" in --tasty-text-muted, then a dimmer one-line hint pointing at the add path. Same tone as the content-area "This folder is empty" state — left-aligned to fit the 196px column.
New string explorer.sidebar.favorites_empty — en "No favorites yet" · ko "아직 즐겨찾기가 없습니다" · add to lang/{en,ko,ja}.toml. The hint reuses existing "Add to favorites" copy. Implementer: replace if !favorites.is_empty() in explorer.rs sidebar() with an always-rendered caption + empty branch.
Sidebar layout — Favorites PINNED to the bottom (2-region split)
The sidebar is not one scroll column any more. It splits into two independent regions: Files takes the remainder at the top and scrolls on its own, and Favorites is pinned to the bottom at a computed fixed height — so however deep the tree gets, the favorites list is always on screen. The boundary is a fixed 1px --tasty-explorer-split-border at a screen coordinate: it does not flow with content, so a short tree leaves empty --tasty-bg-sidebar above the line rather than pulling the line up. Pin height = 240 (--tasty-explorer-favorites-pin-height) while the sidebar body is ≥ 600 (--tasty-explorer-favorites-pin-threshold); below that it becomes 40% of the body height (--tasty-explorer-favorites-pin-ratio), snapped to the 4px grid and floored at 120 (--tasty-explorer-favorites-pin-min-height). Row, caption and empty-state visuals are unchanged — this is a layout change only.
Open decisions resolved (design SoT): (1) threshold basis = the sidebar body height (the split container itself), not the whole explorer tab — the pin only competes with the tree, and this keeps the rule local to sidebar(). (2) scrollbar = the shared .tasty-scroll behaviour, hover-revealed like every other scroll area — the pinned region gets no special always-on bar. (3) slack space at 0–1 favorites stays empty --tasty-bg-sidebar: no filler padding, no vertical centering, so rows always start directly under the caption. (4) boundary weight = the same 1px separator as before — no shadow, no tint; "pinned" is communicated by behaviour (the line never moves, each side scrolls alone), not by extra decoration. (5) 240 ↔ 40% jump is left as a hard switch (no interpolation) as the requester allows, with a 120 floor added so a very short surface still shows caption + ~3 rows. Implementer: split the single ScrollArea in explorer.rs sidebar() into two — ScrollArea::vertical().id_salt("exp_tree") in the top allocation and id_salt("exp_favs") inside a bottom-up allocated strip of the computed height — and draw the separator as the strip's top edge instead of an inline ui.separator().
Short cell — Favorites drops below 240, the cell stops at 180 (2026-10-07 · floor 2026-10-08)
When the explorer is split low, the sidebar body can be shorter than the Favorites floor. Below 240 (--tasty-explorer-favorites-hide-below = the 120 Favorites floor + 120 for Files) the Favorites region is not drawn and Files takes the whole body; it comes back as soon as the body reaches 240 again. The explorer cell itself has a floor: the split drag stops at 180 (--tasty-explorer-min-height). 2026-10-08: the old 160 counted toolbar + status line + two rows but left out the internal tab bar and the Detail header, so the app showed only .. at the floor. 180 is the full stack, top to bottom: internal tabs 28 · toolbar 44 · Detail header 28 · two rows 56 · status line 24. The explorer's internal layout does not change at the floor (no smaller header or status line); the floor follows the stack. The status line always has its place and Detail shows two rows. Nothing is cut mid-row. Outside a drag (2026-10-09 wording): a split (tasty split or the split menu) that would leave an explorer cell under the floor places the new divider so the explorer keeps 180 and the sibling takes the rest. Only explorer cells have a floor; a sibling of another kind has none of its own, but a new split never leaves it under 56 (--tasty-split-sibling-min-height = pane tab strip 28 + one 28 row), so no sliver cell is made. The split is refused (no change) when that 56 can't be kept, when two explorer cells can't both keep 180, or when an explorer cell already under 180 would be split further. User split → info Toast “Not enough room to split this pane”; agent split → error split refused: not enough room. The check uses the largest view showing the workspace; with no window showing it, the floor is skipped. A window resize does not hold the floor — the cell may shrink below 180. Short body: when the content body is under 120 (--tasty-explorer-state-compact-below) every status screen switches to a one-line compact row — 16px glyph, title (ellipsis), and the state's buttons; the OS reason and the sub line move to the title tooltip. Retry and Go up stay on the row. Nothing is clipped.
The Favorites height rule already reads tokens (--tasty-explorer-favorites-pin-height 240, -pin-threshold 600, -pin-ratio 0.4, -pin-min-height 120). The three dimensions scale with the UI zoom like the other explorer tokens; the ratio is unitless and does not.
View modes — Grid · List · Detail
Grid cell (updated): icon shrinks to icon_glyph_size_md (16, was 28) and the label wraps up to 3 lines at font_size_caption (11) with … overflow on the last line — replacing the old 12-char hard cut. Height is fixed to 3 label lines so grid rows stay uniform (short names reserve the space, label top-aligned). CELL_W stays 80. Real side: LayoutJob with wrap.max_rows = 3 + overflow_character = '…', drop truncate(&e.name, 12), recompute cell_h. Cut items dim to ~50% until pasted. A renaming cell swaps its label for an inline Input (extension preserved). Image cells show a thumbnail in the icon slot at implementation time — here a tinted glyph stands in. Row heights (2026-09-29): no Explorer-specific height. List reuses the tree row (--tasty-tree-row-height 22), Detail reuses the shared Table (--tasty-table-cell-height 28 for both header and body rows). The old 24 / 26 literals are retired. Implementer: the Table widget must read table_cell_height for row_height and header_height instead of its font-derived default (~35).
Right-click context menu — target resolves to 4 shapes
Finder/VS Code rule: right-clicking an item inside the selection targets the whole selection; outside it, selection collapses to that one item; on the empty background, the target is the current directory (cwd). Menu items appear/disappear accordingly, grouped by divider into clipboard · file ops · system.
Reuses the shared MenuItem / Popup visual language — no bespoke menu chrome. Shortcut hints are representative; real keys come from Keybindings settings.
Empty / permission / loading / read error · favorite + rename popups
Status screens fill the content area; the two small editors reuse the Popup/rename visual language. Read error (2026-10-07): any failure that is not a permission denial (path gone, not a directory, I/O error, remote read failure) gets its own state instead of falling through to “This folder is empty”: alertTriangle in --tasty-explorer-error-fg (→ accent-danger, as in the file picker error), title “Can't read this folder”, the OS reason as one mono caption line (text-muted, max 200, untranslated), and two buttons: Retry (secondary sm — re-reads the same path) and Go up (ghost sm — opens the parent; hidden at a root). Toolbar, tree and status line stay usable.
Explorer font (family + size, 14px cap) gets an Appearance › Explorer sub-tab mirroring the Terminal/Markdown font sections — same control layout, deferred to the Settings specimen.
Markdown viewer
Markdown — read-only viewer (address bar + body)
The markdown surface is an address-bar chrome over a single vertical scroll body that fills the tile (body on --tasty-md-doc-bg = crust, reloads on file change). The chrome is native UI. The body is an HTML/CSS document in a WebView: colour, heading sizes, leading and block spacing all come from Tasty tokens through theme_css() (maps in content colour and heading hierarchy below). The drawing uses the same values.
The surface leads with a browser-style address bar (path display/edit) + Go button. The body follows the WebView/CSS path (2026-10-07); the earlier egui_commonmark renderer and its library-owned sizes are retired. Removed/redefined content tokens (--tasty-font-size-prose-h2, --tasty-line-height-prose removed; --tasty-font-size-prose-h1 → Heading anchor) are covered in the heading-hierarchy specimen.
Markdown — document background (single bed, webview render path)
Under the webview render channel the markdown body is an HTML/CSS document drawn by the host WebView — there is no focused/unfocused signal on that path, so markdown has exactly one background (unlike the terminal, which swaps two). That bed is --tasty-md-doc-bg → --tasty-surface-markdown-focused-bg = crust. Pure black is a terminal convention (raw TTY) and sits outside the Catppuccin ramp; prose at the 14px cap on #000 halates. crust is the palette's deepest tone, so every content fill still steps up and stays discriminable: crust (page) < mantle (table zebra) < base < surface0 (code + table header) < surface1 (table grid). mantle would have swallowed the zebra stripe; base would have flattened the table's own fill against the page.
Resolves the copy-paste value in [surfaces.markdown].focused_bg (was #000000, identical to terminal). Implementing side: set mocha focused_bg = crust, drop the Latte white override, and keep unfocused_bg as-is — it is dead on the webview path.
Markdown — content colour (WebView + CSS, 2026-10-07)
Markdown renders in a WebView. render.rs theme_css() writes Tasty tokens into CSS variables, and the stylesheet reads only those variables, so the page follows Mocha/Latte 1:1. Code highlighting is highlight.js (offline bundle): hljs_css() maps each hljs-* class to a palette role. Sizes, leading and block spacing are CSS too and belong to the next spec. The tables below are the source of truth (the old egui_commonmark / syntect path is retired).
Both tables follow theme_css() / hljs_css() one row per variable or class; the two diff backgrounds are in the Meta. Both themes follow automatically because the palette roles re-point per theme.
Address bar states · large-file confirm
The address bar is a browser-style path field. Display shows the current file path (mono, secondary); click to edit (focus ring + caret) and type a new path — ↵ or the Go button opens it in place, Esc reverts to the original. A non-.md extension still opens as markdown. Opening a file over 1 MB first raises a surface-scoped confirm — clamped to the tile (not a window scrim), same 360px shell as markdown_open — with Open / Cancel; Cancel opens nothing.
The address bar is the shared editable path field (see Explorer section) with a file leading icon — same field, same edit/navigate/revert contract as the Explorer toolbar, and the same edit-time AutoComplete dropdown (candidates = recent files). Copy: title “Open large file?” · body “Over 1 MB — rendering may be slow.” · size chip formatted (e.g. “3.2 MB”). Surface-scoped (PopupScope::Surface) so it dims only this tile; Cancel replaces nothing. New i18n keys when wiring: markdown.large_file.title / body.
Large file — read deferred after Cancel (2026-10-09)
Cancelling the 1 MB confirm leaves the body on the neutral state block (.tasty-state, same place and colours as the empty-file state). The block now carries a way back: a secondary sm Button “Open file…” under the line, --tasty-space-sm below it. It raises the same confirm again (it does not read the file directly), so the 1 MB rule has one entry point. The ellipsis says a question follows. Hover / active are the Button's own derived states. Local and mirror documents both get it; the CLI tasty markdown reload stays as it is.
WebView: render the button as a <button class="tasty-state-action"> styled from the injected Button variables; it posts the same IPC as the reload path. i18n: markdown.state.large_deferred (line) · markdown.state.large_deferred_open “Open file…”.
Address bar — folder button · local · remote (2026-10-21)
A folder button (folderOpen) now leads the band; it opens the Tasty file picker at the current document's folder with the Markdown filter, and the picked file replaces this surface's document. It sits outside the field and the field keeps its leading kind glyph (file = local, remote = mirror): the glyph says what the field holds, the button is the action, and the remote glyph is the same mirror marker the HTML band uses. The Markdown mirror field is now editable: type a remote path, Enter or Go replaces the document, Esc reverts. Reload joins the folder button in the left cluster, where the HTML band also keeps it. Raw → and ↻ are retired: every button is the shared IconButton ghost sm with an icons/ glyph.
Markdown mirror vs HTML mirror. Same band box, same left-cluster Reload, same remote glyph as the mirror marker, same error row. The one difference is the field: HTML mirror = read-only box (input-readonly tokens) with no Go; Markdown mirror = the normal editable box with Go. The field box is how a user tells “I can type here” apart.
Folder button — states · tooltip strings
The shared IconButton; the only per-surface parts are the glyph and the tooltip. Hover / active use the Theme overlays; focus is the 2px focus ring; disabled (remote disconnected) has no overlay.
Strings (tooltip = aria-label without the ellipsis) — markdown.address.open: Open a file… · 파일 열기… · ファイルを開く… — .open_remote: Open a remote file… · 원격 파일 열기… · リモートのファイルを開く… — .go: Go · 이동 · 移動 — .reload: Reload · 새로 고침 · 再読み込み — .stale: The remote file changed — reload · 원격 파일이 바뀌었습니다 — 새로 고침 · リモートのファイルが変更されました — 再読み込み — .disconnected: Disconnected from the remote · 원격 연결이 끊겼습니다 · リモートから切断されました — .error.not_found: Can't open {path} on the remote: file not found. · 원격에서 {path} 파일을 열 수 없습니다: 파일이 없습니다. · リモートで {path} を開けません: ファイルが見つかりません。 Tooltips wrap at the tooltip max-width (size-240); the ja stale string takes two lines, the rest one.
Remote navigate failed — by kind · still waiting (2026-10-21)
The error row is one shape for every failure (alertCircle, caption text-primary, 24 row, field keeps the typed path with the invalid edge); only the sentence changes. Each kind the host can tell apart gets its own sentence, because the right next step differs: file not found (unchanged) · remote refused — the remote's own one-line reason is shown after the colon as sent (English, not translated; it is the only detail the user can act on) · connection lost before the answer — whether it applied is unknown, so the row says reconnect and try again and claims nothing about the document · cancelled because the connection / mapping changed before sending · internal (the host never got the request). No kind reuses “file not found”. After 30 s without a result the band shows a status row (Spinner + “Still waiting for the remote…”, md-address-pending-fg) in the error row's place — it is not an error, the remote may still answer; the field keeps the typed path in the editing look. The row goes away when the result arrives (success replaces the document; failure swaps in the error row) and on the same triggers that clear an error (next keystroke, Esc). A late result still applies as today.
i18n (new, markdown.address.error.*) — .rejected: Can't open {path} on the remote: {reason} · 원격에서 {path} 파일을 열 수 없습니다: {reason} · リモートで {path} を開けません: {reason} — .connection_lost: Connection lost before the remote answered. Reconnect and try again. · 원격이 응답하기 전에 연결이 끊겼습니다. 다시 연결한 뒤 시도하세요. · リモートの応答前に接続が切れました。再接続してからもう一度お試しください。 — .cancelled: Didn't open {path}: the remote connection changed. Try again. · 원격 연결이 바뀌어 {path} 파일을 열지 않았습니다. 다시 시도하세요. · リモート接続が変わったため {path} を開きませんでした。もう一度お試しください。 — .internal: Couldn't send the request to open {path}. Try again. · {path} 파일을 여는 요청을 보내지 못했습니다. 다시 시도하세요. · {path} を開く要求を送信できませんでした。もう一度お試しください。 — markdown.address.waiting: Still waiting for the remote… · 원격의 응답을 기다리는 중… · リモートの応答を待っています… — existing .error.not_found unchanged. Kind → key: NotFound → not_found · Rejected(reason) → rejected · Disconnected → connection_lost · Cancelled → cancelled · Internal → internal.
Narrow surface · long paths
Buttons never shrink. When the field would fall below 160 (md-address-go-hide-below), Go hides first; Enter still navigates. The field then shrinks to its 96 floor and ellipsises from the left, so the file name stays visible. Folder and Reload are never hidden: Folder is the only pointer route to the picker, and Reload carries the stale signal. The error row ellipsises at its end; the full message is its tooltip.
Keep the tail of the path (the file name) visible: ellipsise the field from the left.
Drop the folder button to make room; hide Go, which Enter replaces.
HTML viewer
Markdown — heading hierarchy (CSS-interpolated, 2026-10-07)
Heading sizes are set by Tasty in CSS: heading_sizes_px() interpolates five steps from h1 = --tasty-font-size-prose-h1 (20, the only cap-exempt content size) down to h6 = body (13) and writes them as --md-h1…--md-h6. Body line-height is 1.6 and block spacing is CSS as well. Weight and colour come from the same token map as the previous spec.
Load-fail / empty / loading states (proposed): a peach-toned "Failed to load" instead of raw Error: text, a centered "This file is empty", and the Spinner for slow loads — all in the Explorer states pattern above. Markdown font gets its own Appearance › Markdown sub-tab. Size, leading and spacing are all Tasty's (CSS).
HTML viewer
HTML — webview chrome (4 states)
The html surface is thin chrome + a native OS WebView overlay — tasty does not theme the page pixels. What tasty paints is the tile skeleton before/instead of the overlay: boundary (region marker), placeholder (no URL), loading (Spinner), error (danger). Same tile (--tasty-bg-panel + 1px border), centered content. The address bar band above these states is specified below (2026-10-12).
HTML — address bar band (2026-10-12)
The page is a native WebView that tasty cannot paint over, so the address bar is a host band above it and the WebView rect starts below the band (same inset idea as the script banner). Composition = the Explorer toolbar recipe: Back · Forward · Reload (IconButton sm) then the PathField box — without the Go button (Enter opens; the band already carries three buttons). Height 40 and --tasty-bg-sidebar + 1px separator match the markdown address bar. Local files show as a path (file:// input is accepted and shown as the path after open); web pages show the full URL. Loading swaps Reload for a Spinner in the same 24 cell. Input errors stand in a 24px row inside the band (danger edge on the field, glyph + message below); they clear on the next keystroke, on Esc, or on a successful open — never on a timer. Stack order under the band: size banner → script banner → page, banner-margin 8 above the first, banner-inset-gap 8 between and below.
An agent changing the address while the user edits does not touch the typed text or focus, and gets no extra marker — Esc reverts to whatever the current address is by then.
Address field ink — idle and read-only real values (2026-10-21)
The idle address and the mirror's read-only address are the real value the user reads, selects and copies, so they need body-text contrast (4.5:1). Both were text-secondary on the field ground (input-bg / input-readonly-bg → surface-raised): 7.10 in Mocha but 4.05 in Latte. They now use text-secondary-raised, the existing role for secondary ink on surface-raised (Mocha = text-secondary, Latte = neutral-1100). Mocha does not change. In Latte the idle and editing values share one ink; editing is still told apart by the focus edge, the ring and the caret. Placeholder, disabled, invalid edge and error row keep their own roles.
Scope. input-readonly-fg is changed for every read-only Input, because the read-only box is surface-raised everywhere and the same 4.05 applies to all of them. Mocha is identical, so only Latte moves (n1000 → n1100). Consumers: Input readOnly (Appearance colour rows at Default; Explorer remote rename while waiting), fp-folder-path-fg (file picker folder mode path readout), the HTML mirror address field. Input ground="raised" read-only sits on state-disabled-fill-raised (one step lower than surface-raised), where n1100 reads higher still. Disabled ink, placeholder and the editing ink are not touched. The Markdown band's idle path had the same text-secondary on the same box; it now uses md-address-value-fg. No new strings.
HTML mirror — remote size banners and the receiving state (2026-10-12)
A mirror receives the entry document first, then each resource the page asks for. One file over 100 KB raises the large-file banner (name, size Tag, Decline / Allow); several waiting files are asked one at a time, with a “+N more waiting” count. Over 2 MB per entry document raises the limit banner (Stop / Continue) and auto receiving pauses. Both use the script banner's inset shell and button boxes, and sit above the script banner (they block rendering; the script banner is about the rendered document). Neither has × — each is a decision; neither takes keyboard focus. While a banner waits the page rect shows Receiving remote files… with a static glyph and “Paused”; while receiving runs it shows a Spinner with bytes · files received, so it reads differently from the local Loading state. Declined files leave a “N skipped” Tag in the read-only field (tooltip lists them) until Reload.
HTML — settings (Appearance › HTML viewer)
Reuses the existing plugin-settings row vocabulary (switch 28×16 · select · number) — no bespoke controls. Toolbar / address bar / find-in-page / retry stay open decisions (not in this pass).
Image viewer
Image — viewer & edit (paint) modes
The image surface is a single horizontal control bar over a canvas. Two modes toggle the bar: viewer (◀ ▶ ↻ ✎ + · filename (i/n) · right-aligned zoom group) and edit / paint (Save · Cancel · undo/redo · brush size · color · zoom). Canvas fills with the --tasty-bg-sidebar tone so the image floats; ≤100% fits to window, >100% pans on drag. ◀ ▶ show only when sibling images exist.
Image — paint bar · floating selection · popups
Edit mode swaps in the paint bar (undo/redo with disabled state, brush-size slider, color swatch defaulting to --tasty-accent-danger). A pasted image becomes a floating selection — accent border + 8 resize handles — that you move/resize before committing. New Image and Save As are small popups.
Defaults (brief §6): metadata status-bar, filmstrip, async loading indicator, rotate/flip/crop tools, and transparency checkerboard are out of scope this pass — viewer + brush/paste paint only. The two canvas states (empty · load failed) are the next spec.
Image — empty canvas vs load failed · Save As name clash (2026-10-09)
The canvas reuses the explorer state screen (glyph · title · sub / OS reason · buttons, and its one-line compact form when the canvas is under 120) on the canvas tone --tasty-bg-sidebar. Empty (surface opened without a file) keeps the single muted line “No image loaded” and no buttons. Load failed is split by cause, each with its own title: missing (path gone or moved), permission (lock glyph, warning tone — the explorer's permission state), decode (damaged or unsupported; the decoder message goes on the mono reason line instead of only the log). Every failure has one button, Retry (secondary sm = the toolbar refresh). The file watcher still reloads by itself within 1 s, and ◀ ▶ in the bar keep working when the folder has other images. Save As opened because a .png of the same name exists gets a caption above the field in accent-warning and the field pre-filled with the next free name, stem selected.
Strings (en / ko / ja): image.state.missing “Image not found” / “이미지를 찾을 수 없습니다” / “画像が見つかりません” · sub “It may have been moved or deleted.” · image.state.permission “Permission denied” / “권한이 없습니다” / “アクセス権がありません” · image.state.decode “Can't open this image” / “이미지를 열 수 없습니다” / “この画像を開けません” · sub “The file is damaged or in an unsupported format.” · image.save_as.exists “{name} already exists. Save the PNG under another name.”
ko / ja confirmed (b10): missing_sub “이동했거나 삭제되었을 수 있습니다.” / “移動または削除された可能性があります。” · permission_sub “이 파일을 읽을 권한이 없습 니다.” / “このファイルを読み取る権限がありません。” · decode_sub “파일이 손상되었거나 지원하지 않는 형식입니다.” / “ファイルが破損しているか、対応していない形式です。” · Retry “다시 시도” / “再試行” · save_as.exists “{name} 파일이 이미 있습니다. 다른 이름으로 PNG를 저장하세요.” / “{name} は既に存在します。別の名前で PNG を保存してください。” · too_large “이미지가 너무 커서 열 수 없습니다” / “画像が大きすぎて開けません” · too_large_sub “한 변이 {px} px를 넘거나 디코딩에 {mem}보다 많은 메모리가 필요합니다.” / “一辺が {px} px を超えるか、デコードに {mem} を超えるメモリが必要です。” · markdown large_deferred_open “파일 열기…” / “ファイルを開く…” (kept). Gallery stays en-only; ko/ja live in these notes and the app's lang files.