mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-25 02:47:33 -06:00
cd9faa677b9e628b6878b8a4d6fa271f9ce997d4
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4a75c57f58 |
Comment balloons edit in place, and the ruler drags page margins (#167) (#289)
Clicking a comment balloon in the markup area selects it like Word: its text becomes editable in place, with Reply, Resolve/Reopen and Delete, and replies are shown (and editable) under the comment they answer. Each edit is written back through a new review.editComment command, one undo step per editing session; the Comments pane uses the same path (it used to write the text without an undo step) and gains a Reply button. The margin edges on the horizontal ruler (left/right) and the vertical ruler (top/bottom) can be dragged to set the section's margins through layout.margins, one undo step per drag, snapped to 1/16 inch and clamped so at least an inch of text remains. Indent markers keep priority where they overlap an edge. Setting margins by number no longer switches off mirrored margins; only a preset decides that. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cc64f3df2d |
Add a System interface theme that follows the OS appearance (#115) (#249)
The interface theme is now a three-way choice: Use system setting, Light or Dark, in File > Options > General and in a new Interface Theme menu in View > Dark Mode. System follows the light/dark appearance egui reports (winit on macOS, Windows and Linux; prefers-color-scheme on the web) at launch and live, and falls back to light when the OS reports none. egui's own theme is now pinned to ours, so an OS change no longer swaps in egui's unstyled style. Saved preferences migrate: the old "dark" switch becomes Dark or Light and is never written again. Light stays the default for new installs. New ui.theme command (light/dark/system); ui.dark still sets Light/Dark. The dark page (View > Switch Modes) is unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e4456efb84 |
Text boxes and floating objects: editing, drag/resize, wrapping, overflow, footnotes (#46)
* fix text boxes: type into a new box, click in and out, Escape leaves it Insert › Text Box left the caret in the body after the box and clicks inside a box never reached its story, so its text could not be edited. - insert.textBox puts the caret in the new box (when anchored in a top-level body paragraph, the only place layout shows box text today). - Layout emits a hit-test-only Placed::TextBox area; story_at prefers a box under the point (body text wins over boxes behind it) and ignores box lines outside their box. - Canvas: click enters a box, clicking outside goes back to the body (not while editing a header/footer); double-click in a box selects a word there. - Escape in a box puts the caret just after it (Document::text_box_anchor). - Editing a box doesn't dim the body or show "Editing header/footer". - Tests: engine (caret in box, typing, undo, table cell unchanged), layout (story_at inside/outside/behind), doc (anchor lookup). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix pasted text boxes sharing the original's text A copied fragment only carried the text box's part id, so a pasted box showed the original's story: editing either changed both, edits made after copying leaked into the paste, and pasting into another document (or Insert › Text from File) pointed at whatever part had that id there, e.g. a header. - Fragment carries copies of the text box stories it shows (nested boxes too). - insert_fragment gives every pasted box a new story of its own; a box whose story can't be found keeps none rather than showing an unrelated part. Nesting is depth-limited and one paste creates at most 4096 stories. - Insert › Text from File uses Document::body_fragment, which brings the file's text boxes along. - Tests: doc (own story, copy-time snapshot, other document with clashing ids, box in a table, fragment without parts, self-nested box), engine (copy/paste/type/undo through commands). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count includes text boxes, footnotes and endnotes (Word's default) The status bar and Word Count dialog counted only the body, so words typed into text boxes (and notes) were missing. - Document::counted_stories / word_count_including_notes: the body plus the text boxes, footnotes and endnotes it actually shows (nested too); orphaned stories (deleted or cut boxes), headers, footers and comments don't count. - Session::count_notes (default on) with Session::word_count for the status bar; review.wordCount takes {"includeTextBoxes"?: bool} and reports it. - Word Count dialog: "Include text boxes, footnotes and endnotes" checkbox. - Document::word_count stays body-only (docx Words property, NUMWORDS, stats). - Tests: doc (used vs orphaned stories, nesting, self-nested box), engine (default on, toggle sticks, deleting a box drops its words). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * canvas: select, drag to move and resize pictures, shapes and text boxes Click a picture or shape (or a text box's border) to select it: a frame with eight handles. Drag it to move it, drag a handle to resize it (pictures keep their proportions from a corner, Shift toggles), arrows nudge a floating one (Ctrl/Alt: 1 pt), Escape cancels a drag. While a text box's text is edited its frame shows dashed with handles. Typing or Enter with a text box selected goes into it. Reusable pieces, no per-kind code: - layout: Placed::Object, one hit area for every picture/shape/text box (replaces the text-box-only item); DocLayout::object_at / object / text_box / find_object (page-hinted, no whole-document scans per frame). - engine: arrange.bounds {width,height,x,y,page} (floating objects keep their anchors on the same page; inline ones float where dropped; another page re-anchors to the nearest line) and arrange.nudge; objects::min_size shared with the UI. - ui: frame.rs, a document-agnostic transform frame (handles, hit testing, drag/resize maths, painting); objects.rs wires it to document objects. - geom: Rect::expand. control: ui.press / ui.release for half-way drags. Rendering: nothing is laid out or rasterised while dragging. The preview is the object's own pixels from the cached page texture (moves, picture resizes) or a light outline (text box/shape resizes); the drop is one command, one relayout, one undo step. Tests: frame maths, layout hit areas, engine move/resize/nudge across anchors and pages, hostile values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * moving an object anchors it to the paragraph it's dropped on, so text wraps A floating object only pushes aside text from its anchor paragraph down, and a move kept the old anchor: drag a text box above its paragraph and the text there ran straight under it. - arrange.bounds now anchors the object to the paragraph under its top edge (the last one starting at or above it on that page) and positions it relative to that paragraph's column and top, as Word does: the text it lands on wraps, and it moves with that text. On a page where no paragraph starts it anchors to the nearest line, positioned on the page. - layout: Placed::Object / ObjectHit carry the paragraph origin that column- and paragraph-relative offsets start from. - engine: edit_obj / edit_float change an object at a position (with_obj uses it). - Test: a box dropped over an earlier paragraph anchors there and the lines beside it stop short of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * text boxes hide text that doesn't fit, as in Word Text past a text box's bottom spilled out over the page below it. - layout: fit_box keeps what fits in a box: whole lines that fit (a line that only partly fits is hidden too) and other items starting inside; the first line always stays so even a tiny box shows something. Done in layout, so the canvas, PNG and PDF agree and hidden text can't be clicked; no renderer changes. Reusable for anything laid out into a fixed height. - BOX_INSET_X / BOX_INSET_Y name the text box internal margins (were 7.2/3.6 literals). - Test: overflow hidden, every shown line inside the box, a tall box shows everything, a minimum-size box shows its first line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * square wrap flows text on both sides of a floating object Text only used the wider side of a floating picture, shape or text box; Word's Square wrap fills both sides when there's room. - para: row_spans gives the spans of a row around the objects in its way (each at least 0.5" wide, else the line goes below); a row cut in two is two Lines sharing top and height, the second marked `beside`. Everything per line (alignment and justification per side, hit testing, caret, selection, drawing, PDF) works unchanged; a row is as tall as its tallest part. - layout: a row stays on one page; line numbers count rows, not parts. - Exclusions that don't overlap a line horizontally no longer narrow it. - Test: text both sides of a mid-column box (same row, left then right, hittable), a too-narrow gap stays empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * drop a deleted or cut text box's text from the document Deleting or cutting a text box left its story in Document::parts: never shown or saved to .docx, but listed by document.inspect and kept in memory. - doc: prune_text_boxes removes the text box stories no box shows (nested ones too); free without text boxes, one paragraph pass otherwise. Session runs it after every editing command. Undo snapshots and the clipboard hold their own copies, so undo and cut/paste are unaffected. - doc: reachable() walks the stories objects lead to; counted_stories (word count) and prune_text_boxes share it, and it walks blocks directly instead of building paragraph paths. - Tests: orphaned and nested-orphan boxes go, shown ones (body, nested, in a header) stay; delete then undo, cut then paste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count's include-text-boxes setting survives a restart The Word Count dialog's "Include text boxes, footnotes and endnotes" was a session field, so it reset to on every launch. - engine: Prefs, the editing preferences the front end saves between runs (count_notes moves there; Session::prefs is the one source of truth). - ui: SavedPrefs pairs UiState with the engine Prefs; WordApp::saved_prefs / restore_prefs. UI fields stay at the top level of ui.json, so existing files still load (the new `editing` section defaults). - wordcraft app: saves and restores SavedPrefs instead of UiState alone. - Tests: an old prefs file loads with Word's default; the setting round-trips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * text boxes and floating objects outside the body; footnotes in text boxes Text boxes in table cells, headers, footers, footnotes and other text boxes showed an empty frame (or nothing), and floating pictures and shapes there weren't drawn. Footnotes inserted inside a text box were never numbered or placed. - layout: place_objects, one routine for the pictures, shapes and text boxes in a paragraph's lines (floating ones drawn, every one's area, text boxes' text), used by body paragraphs and by layout_box (cells, headers, footers, notes, box contents). Floats are positioned against an AnchorFrame (column, paragraph, margins, page); headers and footers pass where the page is, so page/margin-anchored objects land right (a footer is laid out again only when it has floating objects); elsewhere they're placed in their container. Nested boxes show MAX_TEXT_BOX_DEPTH (doc) levels deep. - Placed::Object / ObjectHit carry their story; DocLayout::object(&Pos), object lookups include headers and footers, header_footer_text_box_at. A part's lines are looked up in headers and footers too (caret, typing). - Footnotes: numbered in reading order with text boxes where they are (Document::objects_in_reading_order); a box's footnotes go on the page of its line (Document::notes_in_text_box), sharing the body's reservation. - engine: insert.textBox always puts the caret in the new box; disabled inside a text box (as in Word). - canvas: click into a header/footer's text boxes while editing it; leaving a box returns to where it is; body dimming computed once per frame and used for both the render and its cache key. objects: only body objects are selectable, only body paragraphs' objects movable; frames off the body show without handles. - Tests: boxes in header (inline and page-placed), footer, cell and note show text and take the caret; footnotes in a box numbered and placed in order; a box in a table cell takes the caret and its text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count: a selected object counts the whole document, in the dialog too With a picture, shape or text box selected the status bar showed the document's total but Review > Word Count counted the selection (0 words). - engine: cmd::objects::object_selection, "the selection is exactly one object"; review.wordCount counts the document then. The UI's objects::selected now uses it (one definition). - Test: an object selection counts the document; a text selection itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * review: bound nested text box expansion; tidy-ups Blocker: laying out a text box lays out the boxes inside it, which could fan out unboundedly (a box whose shapes show itself, or chains of boxes each showing the next many times) - 30 shapes made 1.7M layout items. Footnote numbering's walk fanned out the same way. - doc: BoxBudget, one bound for both: a box isn't expanded inside itself, nesting stops at MAX_TEXT_BOX_DEPTH, an outermost box expands at most 64 boxes inside it (so one hostile box can't starve the next page's header boxes) and a pass at most 20,000. - layout: Ctx carries a BoxBudget for the whole layout; place_objects asks it before laying out a box's text. DocLayout::text_boxes reports how many were. - doc: walk_objects (objects_in_reading_order, notes_in_text_box) uses it too. - Tests (crafted documents): a box showing itself 30 times lays out once; a five-level chain fanning out 30 ways stays within the budget, for layout and for the walk; a box inside a box still shows its text. Weakening the budget makes them fail (20,000 boxes / 600,001 visits). Tidy-ups: - para: row spans in a VecDeque (pop_front, not remove(0)). - fit_box: a text box's text area excludes both the top and bottom margins. - theme: Tokens::handle / handle_shadow for the frame handles (light, dark); the drag preview's opacity is a named constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * box budget: no overall cap; bound the .docx writer too; tests An audit of the previous commit found: - The 20,000-box overall cap starved ordinary long documents: one budget per layout, spent by body boxes first, then one per page for header boxes (two for footers with floats), so a long document's header boxes went blank on every page. It wasn't needed: only nesting grows exponentially, and MAX_NESTED caps that per outermost box, keeping the work linear. Removed. - The .docx writer still expanded a shape's story only depth-limited (a box showing itself 30 times saved as ~28,000 nested copies, ~7 MB zipped). It now uses the same BoxBudget, and only for text box parts. - Layout expanded a shape's story whatever its part kind (a shape pointing at a header showed the header's text); now text box parts only, as the walk does. The story's blocks are cloned only once the budget agrees. - Translations of "Count words in text boxes and notes" said footnotes only; now footnotes and endnotes, in each catalog's own terms. - Tests: no timing assert or leftovers; a long document with 400 boxes and a header box shows all of them on every page; notes_in_text_box follows nested boxes once; saving a self-showing box stays small (fails at 7 MB without the bound). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Arrange: nudging or dragging an aligned float keeps it in place first main (#136) reads w:align into Float.h_align/v_align, which overrides the offset; the PR's nudge only changed the offset and drag only reset the anchors. Nudge now converts an aligned axis into a column/paragraph offset that keeps the object where it is, and a drag clears the alignment. Test included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fonts: a lookup that races another thread's load of the same family no longer falls back FontDb::face fell back to Source Sans 3 when its load_cataloged added nothing, which happens when another thread loaded the family between the miss and the load. word::resolve memoises that, so the wrong font stuck for the process; it made layout tests on main flaky (justified_lines_shrink_spaces_in_word_2013_mode, space_shrinking_is_only_for_justified_text failed 6 of 15 runs on origin/main). Look again after loading whatever it returns. Regression test races 8 threads on fresh databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout tests: text box and two-sided wrap tests measure what they mean square_wrap_flows_text_on_both_sides compared xs.last() (the end of a trailing space, which may hang past the edge) with the edge; compare the last non-space cluster's right edge. text_box_hides_text_that_does_not_fit compared a tall box's lines with another document's line count; compare with its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Weeb Labs <weeblabs@VFIEVOX3.Router> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
43c0be5735 |
Zotero integration: cite while you write, with a Zotero tab (#189)
* Zotero 1: range fields and custom document properties round-trip Citation managers' ADDIN fields (Zotero, Mendeley, EndNote) are now kept as range fields: FieldStart/FieldEnd markers with the result as ordinary, formatted content between them, possibly spanning paragraphs (a Zotero bibliography). Before, the result was flattened to plain text and a multi-paragraph field lost its end. - doc: InlineObject::FieldStart/FieldEnd, Document::field_ranges, balance_field_ranges; CustomProp and custom_props with get/set/remove. - docx: read ADDIN fields as ranges (field codes up to 2 MiB), write them back as begin/instr/separate…end; orphan markers are dropped on save so files always hold whole fields. docProps/custom.xml is parsed and regenerated (simple types as text, vectors/blobs verbatim) instead of passed through, so Zotero's ZOTERO_PREF_n settings are editable. - engine: file.properties reads and sets custom properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 2: wire-protocol client and document bridge New crate wordcraft-zotero (L5) speaks Zotero's published word-processor protocol (port 23116; 32-bit transaction id + big-endian length frames, JSON payloads, ERR: replies): - client: sends an integration command (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs) and answers Zotero's calls until Document_complete or the connection closes (cancelled dialog). Answers go through a callback so the app can apply them on its UI thread; run_on_session answers directly. - Bridge: implements Zotero's Document_*/Field_* calls on a Session. Fields are stored as Word's plugin stores them (ADDIN ZOTERO_ range fields; prefs in ZOTERO_PREF_n, with Zotero's ReferenceMark field type kept as Word's Field in the file), so documents stay Word-compatible. Rich text (RTF or HTML) keeps citation formatting; bibliographies get a Bibliography style from setBibliographyStyle; note styles create footnotes/endnotes, and deleting a lone note citation removes its note; placeholder links convert to fields. One transaction = one undo step. Field ids stay stable through inserts and deletes. - Tests: a fake Zotero over a real socket, every call, docx round trip, hostile arguments for every method, random frames (proptest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 3: Zotero in the app and the CLI, tested with the real Zotero - ui.zotero.<command> (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs), ui.zotero.status and ui.zotero.answer. The socket runs on a background thread; each of Zotero's calls is answered on the UI thread against the session, and Zotero's questions become a dialog the session waits on. Web builds report that Zotero needs the desktop app. - wordcraft-cli zotero <command> <file> [--save] [--trace] [--port]. - Bridge: a selection Zotero makes to show a citation is put back when the session ends. - ui_shot: {"wait": ms} keeps frames running for background work. - docs/zotero.md. Checked against Zotero 10.0.6: refresh of a citation and bibliography (APA), its "modified citation" question answered in the app's dialog, RTF results with italics, Bibliography style, prefs round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 4: switching between in-text and note styles moves citations Document_convert and Field_convert now move a citation between the text, footnotes and endnotes, as Zotero asks when the document's style changes between an in-text and a note style: into a new note at the same place, or back into the text where the note mark was (removing a note that held only the citation; keeping one with other text). Field ids survive the move, formatting is kept, and each Zotero session stays one undo step. Checked against Zotero 10.0.6: unlink citations (removeCodes, with its confirmation question) and a refresh after switching the document to Chicago notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 5: Zotero tab in the ribbon A Zotero tab (before Help) with Add/Edit Citation, Add Note, Add/Edit Bibliography, Refresh, Document Preferences and Unlink Citations. The buttons are disabled while a Zotero command is running and in the web build. Three new icons drawn in code (document preferences, unlink, add note); the others reuse the References tab's. Labels and status messages translated (zh-hans, zh-hant, ja). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: Add/Edit Bibliography inserted nothing Zotero reads a newly inserted bibliography field back before filling it, and unlinks a field whose text is empty, taking it for a bibliography the user deleted. Our new fields were empty, so Add/Edit Bibliography ended with Field_removeCode and nothing in the document. New fields now hold "{Citation}" until Zotero writes the result. Found by tracing the session with Zotero 10.0.6; regression test added. wordcraft-cli zotero takes --cmd (run before the Zotero command, e.g. caret.docEnd) to reproduce sessions from a given caret position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: log each command's outcome and Zotero's calls to the app log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cargo.lock: wordcraft-zotero at the workspace version (0.4.0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
0811a80f31 |
Add Ukrainian (uk) interface translation (#91)
* i18n #85: add Ukrainian interface translation Translate the existing 771-entry interface catalog and register Ukrainian for locale detection and persisted selection. Check full catalog coverage, count-neutral labels, preference persistence, and bundled Ukrainian glyph coverage. Validate the translation against Ukrainian dictionaries. Co-authored-by: Codex <noreply@openai.com> * i18n: Ukrainian rows for strings added on main Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: Ukrainian label for Character Border (lands after #60) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bded38dd23 |
Control port: require a per-window key (split from #54) (#124)
* MCP bridge: send the control key, to loopback addresses only
`wordcraft-cli mcp --connect` now sends the app's control key with every
request. It takes the key from WORDCRAFT_CONTROL_KEY, or else from the
key file the app writes for that port, and reads it again on every new
connection, also after an `unauthorized` reply (the app restarted with
a new key).
The key goes only to loopback addresses: 127.0.0.0/8, ::1 and
`localhost`, and only when the connection really leads to a loopback
address. For any other host the bridge sends no key, prints why at
start, and turns the app's `unauthorized` into an error that says the
key was not sent and how to reach the app (through 127.0.0.1, for
example an SSH tunnel). Other host names are never resolved for this.
The new `wordcraft_mcp::control_key` module holds the rule the app and
the bridge share: the settings folder (the one with ui.json), the
Flatpak instance id and `<settings>/control-key.<instance>`
(`local-<port>` outside Flatpak). No new dependency; wasm-safe.
* Control port: every request needs the window's key
Any local program, and possibly a web page, could drive the app through
its loopback control port. Each window started with --control now has
a key:
- once the port is bound, the app writes a random key (32 bytes from
the OS, as hex) to <settings>/control-key.<instance>, mode 0600 on
Unix, replacing a stale file; it removes the file on a clean exit. A
failed bind writes nothing. <instance> is the Flatpak instance id, or
local-<port> outside Flatpak (wordcraft_mcp::control_key, the rule
the MCP bridge reads it with). The settings folder is the one that
holds ui.json; prefs_path now uses the same helper.
- every request needs the key: top-level "key", or
{"method":"auth","key":...} once per connection. Only auth reads
params.key; elsewhere it stays an ordinary parameter (ui.key). The key
is checked on every request, in time that does not depend on where a
wrong key differs. A missing or wrong key gets
{"ok":false,"error":"unauthorized"} and the connection is closed.
- a line over 1 MiB gets "line too long" and the connection is closed
(the rest of the line is drained first, so the client reads the
error).
- a connection that sends no authorised request within 30 s of being
accepted is closed; blank lines and bad JSON do not extend that
limit. After that it may stay idle.
* Docs: control-port keys
docs/control-protocol.md gets a Keys section: where the key file is
(per platform and per instance), top-level "key" or auth once per
connection, `unauthorized` and the closed connection, the 1 MiB line
limit, the 30 s limit for the first authorised request, and how the MCP
bridge finds the key and sends it only to loopback addresses (an SSH
tunnel for an app on another machine). docs/mcp.md points there, and
the agent guides (AGENTS.md, CLAUDE.md) show the key in their control
channel example.
* Control key: the key-file helpers in their own small crate
wordcraft-control-key (standard library only, Standalone in the layering
table) holds where the key file lives and how to read it. The app and the
MCP bridge both use it, so the app no longer depends on wordcraft-mcp.
* Cargo.lock: wordcraft-control-key follows the workspace version (0.4.0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: lpgn <2556951+lpgn@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ae79726ab6 |
i18n: add Brazilian Portuguese (pt-br) interface translation (#127) (#128)
771-entry pt-br.tsv catalog written clean-room from the English labels, one LANGUAGES row, locale/persistence/coverage/font regression tests, and README, ROADMAP, control-protocol and ATTRIBUTION updates. |
||
|
|
780fd7a5af |
Control server closes on non-JSON input; hyperlinks follow only http/https/mailto (#34) (#72)
The control server answered a line that wasn't JSON with an error and kept reading, so an HTTP request to the port (for example a web page's fetch to 127.0.0.1) could carry a command such as file.saveAs in its body. It now replies once and closes the connection on the first bad line; HTTP request lines never parse, so nothing after them runs. Ctrl/Cmd+click on a hyperlink passed any URL to the system. It now follows only http, https and mailto links, and shows a status message otherwise, so a document can't launch file: paths or custom-scheme handlers. Tests cover JSON requests, closing on bad JSON, an HTTP POST carrying a command, and the link allow-list. The control protocol docs note the closing behaviour. Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d7d9702bda |
Interface languages: follow the system, or pick one in Options (#8) (#12)
WordCraft's interface can now be shown in Simplified Chinese, Traditional Chinese or Japanese as well as English. The new `language` setting is `auto` by default: the UI follows the operating system's preferred languages (Windows display language, macOS preferred languages, LANG/LC_* on Unix, via sys-locale; WORDCRAFT_LANGUAGE overrides) when there is a catalog for one of them, and English otherwise. File > Options > Interface language picks one by hand, and the `ui.language` command does the same for scripts and agents. The setting persists with the other UI preferences. The i18n module is PdfCraft's (catalog format, parser, locale matching), trimmed to plain entries. English strings in the code are the keys; the ribbon helpers (group, big, small, split, menu_button, menu items, tooltips and ribbon locations), tabs, Backstage, Options, dialogs, panes, the status bar, the context menu and command search translate at draw time. Command ids, documents, the CLI, MCP and the control channel stay English. Catalogs hold ~780 entries per language, written clean-room from the English meaning. Chinese interfaces put the craft-fonts Chinese face before the Japanese one so a line never mixes glyph styles; fonts are reinstalled when the language changes. Fixes #8. Claude-Session: https://claude.ai/code/session_019FczntrDNYd6DupyBdpbL5 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <shared2@storyteller.ai> |
||
|
|
435194f7c2 |
M0.9–M0.12: CLI, MCP server, xtask (layers/assets/parity/ci), icon placeholder, AGENTS/ATTRIBUTION/NOTICE, protocol docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |