Files
wordcraft/docs/control-protocol.md
T
1ff68d64e3 i18n: Arabic interface with mirrored RTL shell (+ parity docs) (#5370)
* Arabic interface: mirrored right-to-left window and Arabic UI fonts

Adds the Arabic (`ar`) interface language's code: its catalogs are craft-i18n's
`locales/ar`, so this changes no catalog.

- `ar` is registered in `i18n::LANGUAGES` and `Lang::rtl()` says it is written
  right to left; its translations are used as written (egui's text layout
  orders each line's bidi text itself). A ribbon location reads from the right
  with a leftwards mark.
- `rtl.rs`: the window is mirrored. Containers open in `rtl::root`; columns,
  grids, checkboxes, radio buttons, menus, rows, panels, the dialog tab strip
  and the ribbon's scrolling row follow the language's direction. Title bar,
  status bar, ribbon tabs and groups, File pages (including Print, with the
  settings on the right and a solid scrollbar gutter), dialogs, menus and the
  task panes (their docking edge swaps) are laid out from the right. egui's
  `Grid` panics in a right-aligned layout, so grids go through `rtl::grid`.
- Arabic UI font fallback: the craft-fonts Arabic face, else an installed
  Arabic font, loaded when the interface is Arabic.
- The "Auto (200%)" size label keeps its percent sign inside the isolated
  number.
- Tests: Arabic locale, saved preference and lookup order on fixture catalogs;
  an interface audit (skipped without the craft-i18n Arabic catalog) that lays
  the app out right to left at several widths, checks tab, group, pane, Print
  and Go To positions and that nothing panics.

The Arabic work builds on Abdelhaq El Amraoui's mirrored-interface PR (#737).

Co-Authored-By: Abdelhaq El Amraoui <abdelhaqelamraoui@gmail.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Arabic interface: pin craft-i18n at a commit with the Arabic catalog; review fixes

- craft-i18n.rev and the Flatpak manifest pin 19358491 (craft-i18n main), the
  first commit line that has locales/ar/wordcraft.tsv. The old pin had no
  Arabic catalog, so `ar` was registered but not embedded and two i18n tests
  (the_language_setting_is_a_ui_command_and_persists,
  the_languages_offered_are_the_embedded_ones_of_the_list) failed in CI.
- The Arabic font fallback is requested for any right-to-left interface
  (`Lang::rtl()`), not only after the language picker sets the flag, so an
  automatic or saved Arabic gets the installed Arabic face.
- The file browser, the update window and Reading Mode's toolbar run their
  bodies in `rtl::root`, so their far-side buttons and rows are mirrored.
- A group in a right-to-left ribbon row is measured from the right edge: the
  cursor's left edge is negative infinity there, so its width was NaN and the
  ribbon planner dropped every group, never compacting a narrow Arabic window.
  A narrow window now folds groups into drop-downs.
- The mirrored checkbox and radio marks are labelled by their text, so screen
  readers name them.
- Tests: group width in a right-to-left row; the Arabic interface audit checks
  Reading Mode's toolbar.

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

* Arabic interface: ask for the installed Arabic font separately from the CJK one

A previous commit made every right-to-left interface request the installed CJK
fallback font (`system_cjk`) so that the installed Arabic font would load. That
also made every Arabic launch read a large system CJK font file when no
craft-fonts input is embedded (the Flatpak build has none).

`install_ui_fonts` now passes a separate `system_arabic` flag (`Lang::rtl()`):
the installed Arabic font is read for it, and `system_cjk` is again set only
by a CJK interface or by CJK text on screen. `WordApp::fonts_system_arabic`
remembers what was installed, so a language change reinstalls the fonts.

Tests: the_installed_arabic_font_is_asked_for_by_arabic_alone (the Arabic flag
adds the installed font only when none is embedded, and the CJK request alone
adds no Arabic font). Checked a headless screenshot of the Arabic interface
without craft-fonts: the installed Arabic font draws the labels.

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

* Fix the AutoSave switch knob: on is at the right, mirrored in Arabic

The title bar's AutoSave knob was placed with `lead_x(rtl == on, ..)`, which
reverses main's mapping in both directions: left to right it drew the knob at
the left when the switch was on, and right to left at the right. main draws it
at the right when on and at the left when off, so the mirrored interface must
draw it at the left when on: `rtl != on`.

The position is now `autosave_knob_x(rtl, on, track)`, pinned by a test for all
four cases. A headless screenshot of the off switch shows the knob at the left
in English and at the right in Arabic.

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

* Arabic interface: file names and paths in right-to-left lines; kerning row order

Tester feedback on the Arabic interface:

- File > Open's Recent list, the built-in file browser and the title bar drew
  file names and paths raw, from the left edge. In an Arabic line the marks of
  a name ("(نهائي) 2.docx") and the folders of a path with Arabic parts were
  scrambled. Rows now start at the side reading starts from (`rtl::lead_x`),
  names go through `i18n::isolate` (a first-strong isolate, with a left-to-right
  mark before the extension so `2.docx` stays `2.docx`) and paths through
  `i18n::isolate_path` (a left-to-right isolate with a mark after every
  separator, so neighbouring Arabic folders do not swap). Other languages get
  the text untouched; saved and spoken text never carries the marks.
- Font dialog, Kerning row: the dialog's grid is drawn left to right inside a
  right-to-left window, so "Points and above" came after its number; in an
  Arabic interface it now comes first, so the line reads "14 نقطة فأكثر" from the
  right. (The grid's columns themselves still keep their left-to-right order.)

Tests: names_and_paths_are_isolated_in_a_right_to_left_interface.

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

* Arabic interface: mirror through craft_ui::direction and Style::right_to_left

The suite's craft-ui now has the direction helpers (`craft_ui::direction`) and
its egui fork a `Style::right_to_left` that mirrors egui's own chrome. Use them
instead of our parallel wrappers:

- `rtl::sync(ctx)` applies the interface direction every frame the language's
  changes (`direction::apply`): it sets the shared direction state and
  `Style::right_to_left` on every theme, which draws check boxes and radio
  buttons with the mark at the reading start, mirrors submenus, scroll bars,
  title bars and the combo boxes' arrows (the Print page's choice lists, the
  font list) and opens combo menus from the right.
- `rtl::checkbox`, `radio_value`, `radio` and `indeterminate_checkbox` are
  gone: about 200 call sites use plain `ui.checkbox`, `ui.radio_value`, ... and
  egui does the mirroring (and names the control by its text, as a plain widget).
- `lead_x`, `lead_center`, `lead_top`, `start_panel` and `end_panel` delegate to
  `craft_ui::direction`. `root`, `flows`, `island`, `grid`, `menu`, `vertical`,
  `far_side` and the other layout helpers stay: the shared crate has no
  equivalent that fits them.

Visual changes: the combo boxes' drop-down arrows and lists are mirrored in
Arabic; nothing changes in left-to-right languages (checked headless
screenshots of the English main window, Font dialog and Print page).

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

* Recent files and the file browser: full path on hover, long names keep their start

Review feedback:

- Each row of File > Open's Recent list shows the document's full path as its
  tooltip (the path line under the name can be longer than the row).
- A long name in the built-in file browser is laid out in one row, with an
  ellipsis at its end, from the side reading starts from, instead of being
  clipped at the row's edge: in a right-to-left interface the old text anchored
  at the right edge cut the end of an Arabic name and the start of a Latin one.
  The whole name is the row's tooltip when it was shortened.

Checked headless screenshots of the file browser with a long English and a long
Arabic name.

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

* Font combos in a right-to-left row: the arrow joins its field on the left

The ribbon's font name and size boxes are drawn from a field and a separate
arrow box that share one outline: the field is square and the arrow's box is
rounded on the side where the arrow goes, painted a border width under the
field. That assumed the arrow is right of the field. In a right-to-left row
(the Arabic ribbon) the arrow comes after the field on its left, so the field
was square on the wrong side and the arrow box rounded on the side facing the
field: two boxes with a gap-like seam, the arrow looking outside the frame.

`arrow_box_shape(rtl, ..)` mirrors the radii and the underlap, and the field's
square corners are chosen by the row's direction (the nw/sw pair in right to
left). Left-to-right rows are drawn exactly as before (English before/after
crops are identical).

Test: the_arrow_box_is_rounded_away_from_its_field.

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

* Pin craft-i18n 4612850 (merged Arabic WordCraft rows, craft-i18n#76)

The pin and the Flatpak manifest commit move from 1935849 to 4612850, which is on
craft-i18n main and adds the 70 Arabic rows this branch relies on.

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

* Pin craft-i18n cac2cf0 (Arabic number agreement and status-bar rows)

Moves the pin and the Flatpak manifest commit from 4612850 to the current craft-i18n
main, which includes craft-i18n#95 (48bed03) and #81 (ef58ff3).

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

* Arabic: isolate paths shown beside labels; mirror the Print page zoom

File > Info shows Location as its own left-to-right path label (cut short with the full path on hover, not wrapped), and the open-failure, Exported and dictionary-file messages isolate their paths. The Print preview zoom puts minus at the slider's left end and plus at its right in Arabic, as the status bar does.

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

* Arabic: mirror the label/field rows of the Font, Info, Paragraph, Page Setup, Insert Table and Options forms

rtl::Table lays a form out as an egui Grid in a left-to-right interface (same id, columns and spacing, so the output is unchanged) and, where the interface is mirrored, with each row's cells running from the right, every column as wide as its widest cell. File > Info puts Properties at the right and Statistics after it, and the Font dialog's kerning row reads 14 points and above.

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

* docs: record Arabic mirrored interface landing (#1195)

Update the language counts, Arabic parity row, gap #11, and UI/ROADMAP
status so they match the craft-i18n catalog and mirrored shell already
on this branch.

* Fix Organizer Copy arrow in RTL and refresh roadmap status.

Point the Copy glyph at the Document column when the shell is mirrored,
and bump docs/roadmap.md status/revision history for Arabic UI (#1195).

* fix(ui): keep pane dock left/right physical in RTL

ui.paneDock and view.stylesPaneDock now store and report physical window sides; only unset defaults still open on the reading-start edge. Also wrap the language-packs grid for RTL, document the contract, and load a system Arabic face for Arabic names in LTR UIs.

* docs: clarify mirrored default pane edges in RTL

Start-default panes open on the physical right in Arabic; end-default
panes open on the physical left — not every never-docked pane on the right.

* refactor(ui): one PaneState for a saved pane place

The four saved-dock arms built the same PaneState; only the place and
whether it was set explicitly differ.

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

* style(ui): rustfmt the panes re-exports

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

---------

Co-authored-by: The Legend Of MTS Dev <tlo.mts.tsh@gmail.com>
Co-authored-by: Abdelhaq El Amraoui <abdelhaqelamraoui@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: email <email@email.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-03 15:00:45 +01:00

16 KiB
Raw Blame History

Control protocol

Start the app with a control port: wordcraft --control 7981 (or WORDCRAFT_CONTROL_PORT=7981). A missing or invalid port after --control (not 1–65535) prints an error and exits with status 2. It listens on 127.0.0.1 only. Send one JSON object per line, with the window's key (see Keys); each gets one reply line:

{"id": 1, "key": "…", "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}}}
{"id": 1, "ok": true, "result": {"anchor": {...}, "focus": {...}}}

Errors come back as {"id": …, "ok": false, "error": "message"}. A failed command leaves the document unchanged. A line that isn't valid JSON gets one error reply, then the server closes the connection (so an HTTP request sent to the port can't run a command); reconnect to continue.

Keys

Any local program (and possibly a web page) can reach a loopback port, so every request needs the window's key. Once the port is bound, the app writes a random key (256 bits, as hex) to <settings>/control-key.<instance> and removes the file when the window closes; a new start writes a new key. WORDCRAFT_PROFILE_DIR overrides the whole profile folder for isolated runs, including recovery copies. See local recovery. <settings> is the folder that holds the preferences: on Linux $XDG_CONFIG_HOME/wordcraft (by default ~/.config/wordcraft), on macOS ~/Library/Application Support/WordCraft, on Windows %APPDATA%\WordCraft. <instance> is the Flatpak instance id inside Flatpak, and local-<port> otherwise. On Unix the file has mode 0600.

cat ~/.config/wordcraft/control-key.local-7981   # Linux, outside Flatpak, port 7981
  • Send the key as top-level "key" with every request, or once per connection with {"method": "auth", "key": "…"} (reply {"ok": true, "result": {}}); later requests on that connection may leave key out. Only auth reads params.key; for other methods params.key is an ordinary parameter (ui.key {"key": "Enter"} presses Enter).
  • The key is checked on every request. A missing or wrong key gets {"ok": false, "error": "unauthorized"}, and the app closes the connection.
  • A line over 1 MiB gets {"ok": false, "error": "line too long"}, and the app closes the connection.
  • A connection that sends no authorised request within 30 s of being accepted is closed. That is one limit: blank lines and bad JSON do not extend it. After an authorised request the connection may stay idle.

wordcraft-cli mcp --connect takes the key from WORDCRAFT_CONTROL_KEY, or else from the key file for the port it connects to, and reads it again on every new connection (the app may have restarted). It sends the key only to the app's own address, 127.0.0.1 (localhost connects there too, never to ::1): another loopback address such as ::1 or 127.0.0.2 is not where the app listens, so whoever listens there gets no key (#3699). For any other host it sends no key, says so, and the app refuses its requests: to drive an app on another machine, forward its port to 127.0.0.1 (for example ssh -L 7981:127.0.0.1:7981 host) and set WORDCRAFT_CONTROL_KEY to that app's key.

Methods

Method Params Result
engine.execute command, params, doc? the command's result (any id from engine.commands, including UI commands ui.tab, ui.dialog ({"name": dialog, "part"?: table style part}; part opens New / Modify Table Style (newTableStyle / modifyTableStyle) on that part: wholeTable, headerRow, firstColumn, bandedRows, nwCell… as table.modifyStyle names them), ui.backstage, ui.zotero.* (see docs/zotero.md), ui.language ({"value": "auto"|"en"|"zh-hans"|"zh-hant"|"ja"|"pt-br"|"es"|"uk"|"sr"|"sr-latn"|"et"|"tr"|"ru"|"pl"|"de"|"it"|"cs"|"hu"|"fr"}), ui.theme ({"value": "system"|"light"|"dark"}; system follows the OS appearance, light when it reports none), ui.interfaceSize ({"value": "auto"} or {"percent": 50–300}, clamped; no value reads it; auto keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's percent is the size drawn at and auto whether it follows the system; document zoom stays view.zoom), ui.animations ({"value": "system"|"on"|"off"}; no value reads it; system follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from prefers-reduced-motion, and means on where the system doesn't say; the result's enabled is what the setting comes to and system what the system reports, null when it doesn't say), ui.fileBrowser (desktop: {"purpose": "document"|"picture"|"recipients"|"save"} opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), ui.saveAsTemplate (File › Save As › Save as Word Template: the Save dialog for a .dotx, starting in the user templates folder; the result's dir is that folder, null on the web, #2852), ui.caretAnimation ({"value": true|false}; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), ui.startScreen ({"value": true|false}; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), ui.alignmentGuides ({"value": true|false}; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), ui.quickAccessToolbar ({"value": true|false}; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), ui.focusRibbon ({"value": true|false}; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), ui.contextMenu (opens the page's context menu below the caret, as Shift+F10 does), ui.paneDock ({"pane": "navigation"|"clipboard"|"styles"|"styleInspector"|"accessibility"|"altText"|"formatShape"|"restrictEditing"|"comments"|"smartArtText", "dock"?: "left"|"right"|"float", "before"?: pane|null}; no dock reads it; docks or floats a task pane as its menu does, kept between runs; Styles is view.stylesPaneDock, #1756; before puts the pane directly before that pane in the order, outside it on their edge, as a drop over a docked pane's outer third does, and null after every pane; the order is kept between runs)…)
engine.commands — every command: id, label, location, shortcut, params, enabled
document.inspect text?, doc? blocks (text, style, runs, lists, tables), parts, sections, selection, pages
ui.inspect — UI state, view, dialog, window size, page rects on screen, caret, comment balloons (balloons: id + screen rect; balloon: the selected one), perf; document: the active document's id; documents: the open documents (id, title, path, dirty, active)
ui.click x, y, button?, count?, shift?, cmd?, alt? real pointer input at window coordinates
ui.clickText page (0-based), x, y (points from the page's top-left), count? click inside a page
ui.move / ui.drag x,y / x,y,toX,toY,steps? pointer move / drag
ui.press / ui.release x,y, modifiers primary button down / up there (with ui.move between: a drag you can screenshot halfway)
ui.wheel x, y, dy, dx?, unit? (point/line/page), phase? (start/move/end), modifiers mouse-wheel or trackpad scroll at window coordinates (dy < 0 scrolls down)
ui.key key (B, Enter, Left…), shift?, alt?, cmd? key press through egui (shortcuts apply)
ui.text text typed text through egui
ui.screenshot path? PNG of the window: written to path, or without one returned as base64 png in the reply
ui.render path?, page? (1-based), scale?, doc? PNG of a page, rendered by the engine: written to path, or without one returned as base64 png in the reply (never a fixed name in the shared temporary folder)
ui.parity — feature-catalog parity
ui.resize width, height asks for that window content size in points; sizes that aren't positive and finite are refused, the rest are clamped to the app's minimum (760 × 480) and to the largest surface the GPU can draw (at most 16384; that limit wins over the minimum), and the reply's width and height are the size asked for
ui.focus / app.quit window control

Any other method name is treated as a command id: {"method": "format.bold"} works.

Task pane docking. ui.paneDock and view.stylesPaneDock use physical left / right (the window's left and right edges), matching Dock Left / Dock Right in the pane menu. That stays true in a right-to-left interface language: {"dock":"left"} always puts the pane on the physical left and the command reports "left". A pane that has never been docked keeps its default start-/end-edge role when mirrored: start-default panes (e.g. Navigation) open on the physical right in Arabic, end-default panes (e.g. Comments) on the physical left. Saved docks and explicit requests are never flipped.

Language packs (see MCP) are commands like any other: proof.packs, proof.installPack {"locale": "nl"} and proof.removePack {"locale": "nl"}. Through the control channel they act on the running app's packs, and install only while its downloading setting is on; they never turn it on, and neither does a ui.click or ui.key on the app's Install, Show available languages or Refresh buttons. An install or a refresh runs in the background ("started": true); proof.packs reports its progress, then the result. ui.dialog {"name": "languageData"} opens About › Language data.

Open documents

Several documents can be open at once. Each has an id, a whole number that stays the same while the document is open and is never reused by the running app. On the desktop each open document has a native window of its own; the web app shows them one at a time in its one window. One document is the active one: on the desktop, the document of the window focused last (the active window); in the browser, the one the window shows. Commands work on it unless told otherwise.

  • file.new opens another document in a new window, focused, and makes it active. file.open does the same with a file; it replaces a blank document nobody has used yet in that document's window, and a file that is already open becomes active again and its window comes to the front ("alreadyOpen": true). Its result carries the document's id.
  • view.switchWindows lists the open documents ({"documents": [{"id", "title", "path", "dirty", "active"}]}); with {"id": n} it makes that document active and brings its window to the front.
  • file.closeDocument {"id"?, "force"?} closes a document; its window closes with it unless it is the last window.
  • engine.execute, document.inspect and ui.render take doc, the id of the document to work on, next to their other params. The command runs on that document and the active window stays active. A dialog or page the command opens (Save As, Print) opens in that document's window, which comes to the front:
{"id": 2, "key": "…", "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}, "doc": 3}}

doc left out, or null, is the active document. An id that names no open document, or a value that isn't a whole number from 0 up, is an error and nothing runs. Window commands (ui.*) work on the active window only: with the id of another document they are an error.

The active window

Input and window methods go to the active window, the one focused last: ui.click, ui.key, ui.text, ui.wheel and the other input methods (coordinates are that window's), ui.screenshot (a PNG of that window), ui.inspect (its size), ui.resize and ui.focus. To work in another window, make its document active first with view.switchWindows {"id": n}.

The CharProps and ParaProps that format.set, para.set and styles.modify take are listed in MCP server.

Positions are {"story": "body" | {"part": n}, "path": [block, row, cell, block, …], "off": byteOffset}.

Keyboard and text input

ui.key and ui.text go to this window's focused widget even when another app is in front (#2420). A focused Find field receives the text; a document receives it only while its canvas owns keyboard focus. Shortcuts, dialogs and document protection still apply. These methods do not activate the native window. ui.focus separately requests native activation.

The {"queued": true} reply acknowledges the input queue, not a completed edit. Input is applied on subsequent UI frames. Inspect the resulting document or widget state before continuing with an operation that depends on it; do not replay an edit simply because its effect is not immediate.

Closing the document

engine.execute with file.close never shows the Save / Don't Save / Cancel prompt. On a document with unsaved changes it replies with an error (the app keeps running and the edits stay); save first (file.save), or pass {"force": true} to discard the changes and close. The bare form {"method": "file.close", "force": true} (a method name that is a command id) is guarded the same way, and only a boolean true counts as force. A document without unsaved changes closes at once (#1268). Closing closes the document and its window, and the window focused before it becomes the active one. The last window stays open with no page, and file.new or file.open goes on from there (#1712); closing it doesn't quit the app. With doc naming a document in the background, file.close closes that document and its window under the same rule (unsaved changes need {"force": true}), and the active window stays active.

Native printing

engine.execute can call ui.printSettings with {} to inspect the printer list, selected printer, copies, duplex, grayscale, discovery/submission status and backend receipt/error. {"refresh":true} starts background discovery; poll the same command until discovering is false. Set printer to a discovered name, integer copies from 1 to 999, duplex to printerDefault, off, longEdge or shortEdge, and boolean grayscale. Invalid updates are rejected without changing earlier settings. ui.printSubmit explicitly submits the current print-ready PDF without opening a dialog; inspect ui.printSettings until busy becomes false and check status/detail. The browser keeps its own print hook. PDF export remains separate.

ui.printSubmit / ui.print also accept the upstream PDF page settings (range, pages, parity, copies, collate). Native printing renders the selected pages once and passes copies/collation to the spooler; the browser and file.printPdf keep copies in the PDF. Omitting native copies uses the configured ui.printSettings value. collate defaults to File › Print's setting; invalid ranges submit no job. paper (none, or a Layout › Size name such as Letter, Legal, A4) is Scale to paper size (#2108): every page is scaled onto that paper and centred, and native jobs ask the spooler for that media; the document's page size is unchanged. file.printPdf takes it too; an unknown paper submits no job.

engine.commands {"search":"print"} lists the engine print commands and the app-owned ui.printSettings, ui.printSubmit, and legacy ui.print alias, with current enablement and their parameter documentation. Search is case-insensitive across IDs, labels, locations and parameter descriptions. Listing commands never discovers printers, submits a job or opens a dialog. Settings inspection is always available; submission becomes enabled when the native printer is ready, or when the browser host provides its print flow.