Commit Graph
3244 Commits
Author SHA1 Message Date
Brandon Thomas 774314a351 Merge pull request #4050 from dotCooCoo/fix/screens-filename-is-file-name
Export for Screens: <filename> takes the saved document's file name
2026-10-25 10:37:45 -04:00
Brandon Thomas e8c92f6bee Merge pull request #4081 from dotCooCoo/fix/flat-logo-bar-three-pixels-long
Image Trace: Flat Logo finds a bar two pixels wide and three pixels long in its own color
2026-10-25 10:37:40 -04:00
Brandon Thomas f775a31a80 Merge pull request #4117 from zsoltmar/fix/3536-pen-cursor-axis
Pen cursors: a fountain-pen nib on the selection arrow's axis
2026-10-25 10:37:36 -04:00
Brandon Thomas d51660d46e Merge pull request #4064 from dotCooCoo/feat/pencil-continue-line-cursor
Pencil: a path-continuation cursor shows over an end a straight line continues
2026-10-25 10:37:33 -04:00
Brandon Thomas c95d1c403c Merge pull request #4053 from marcoarnulfo/fix/icc-reload-direct-cache
Color management: loading the working RGB profile again under its name takes effect at once
2026-10-25 10:37:29 -04:00
Brandon Thomas 0d50e5f6ce Merge pull request #4059 from dotCooCoo/fix/new-symbol-threaded-text-keeps-its-text
Threaded text: New Symbol on a frame no longer duplicates its text
2026-10-25 10:37:26 -04:00
Brandon Thomas a43a99f531 Merge pull request #4056 from marcoarnulfo/fix/set-bounds-compound-member
Properties and Transform: X, Y, W and H of a compound path's member set only that path
2026-10-25 10:37:22 -04:00
Brandon Thomas 1d1d0192ba Merge pull request #4072 from dotCooCoo/fix/recovery-threaded-continuation-marks
Type: a recovered document keeps continued paragraphs in threaded frames
2026-10-25 10:37:19 -04:00
Brandon Thomas d57bcc3a39 Merge pull request #4047 from dotCooCoo/fix/rasterize-threaded-text-keeps-its-text
Threaded text: Rasterize on a frame no longer duplicates its text
2026-10-25 10:37:15 -04:00
Brandon Thomas 4e3c2581c4 Merge pull request #4073 from dotCooCoo/fix/command-chord-test-on-mac
Shortcuts: the Ctrl+Alt chord test presses Cmd on a Mac
2026-10-25 10:37:06 -04:00
Brandon Thomas 33f4246464 Merge pull request #4096 from dotCooCoo/fix/cmd-escape-test-on-windows
Shortcuts: the Cmd+Escape test expects no binding on Windows
2026-10-25 10:37:03 -04:00
Brandon Thomas 9bc0a545a6 Merge pull request #4131 from Silvertree2010/fix/h2469-base64-decode-accepts-misplaced
Reject misplaced or excess '=' padding in base64_decode
2026-10-25 10:36:59 -04:00
Brandon Thomas ca02626de0 Merge pull request #4123 from Silvertree2010/fix/h2465-export-for-screens-size-parser-a
Refuse signs, exponents and non-numeric text in Export for Screens sizes
2026-10-25 10:36:56 -04:00
NetterSilverandClaude Haiku 5.5 7a9818679f Reject misplaced or excess '=' padding in base64_decode
Found while testing crates/format/src/lib.rs:690-717 (base64_decode)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:00:58 +00:00
NetterSilverandClaude Haiku 5.5 76f8574b4b Reject exponent notation in the pica form of Unit::parse
Found while testing crates/doc/src/lib.rs:363-373 (parse_measure pica branch)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 10:56:54 +00:00
NetterSilverandClaude Haiku 5.5 50b067c089 Refuse signs, exponents and non-numeric text in Export for Screens sizes
Found while testing crates/engine/src/cmd/fileio/screens.rs:49 (ScreenSize::parse)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 11:16:58 +01:00
Zsolt van den Már de18d80399 Pen cursors: a fountain-pen nib on the selection arrow's axis
The old pen glyph was a crude pentagon with a floating dot and a stray
stem. Redraw it as the precision instrument it is: the pen-tool icon's
nib — sharp point on the hotspot, shoulders flaring to their widest
two-thirds back, a narrower flat heel, a slit widening into the round
breather hole — swung round onto the selection arrow's midline
(arrow_axis), so holding Cmd/Ctrl to swap between the Pen and the
selection tools no longer rotates the cursor under the pointer.

Badges keep their meanings (*, +, -, o, /, j, ^, ~) and multi-stroke
ones now draw all halos under all ink via strokes(), so neighbouring
strokes no longer cut each other into blobs.
2026-10-25 00:19:53 -07:00
dotCooCoo eec78cfdf8 Shortcuts: the Cmd+Escape test expects no binding on Windows
craft-keymap reserves Cmd+Escape (Ctrl+Esc, the Start menu) on Windows,
so since #3119 binding Show Grid to it there is refused and Cmd+Escape
goes to the tool. a_command_on_cmd_escape_runs_without_the_tools_escape
asserted the bound behavior on every platform and failed on Windows.
On Windows it now checks that Cmd+Escape stays unbound and ends the
Pen tool's path; elsewhere it checks the bound behavior as before.
2026-10-24 16:15:50 -07:00
dotCooCoo be8c4b2010 Shortcuts: the Ctrl+Alt chord test presses Cmd on a Mac
ctrl_alt_chords_without_typed_text_still_run sent Ctrl+L and Ctrl+Alt+L
as egui reports them off the Mac (ctrl and command held). Since #3119
the keymap reads a Mac's Ctrl as a key of its own, so on macOS the test
pressed Cmd+Ctrl+L, which has no binding, and failed with
("Hi", 1) != ("Hi", 2).

The test now holds the command key egui reports on the platform the
keymap follows: Cmd on a Mac, Ctrl elsewhere. Off the Mac it sends the
same events as before.
2026-10-24 13:53:16 -07:00
dotCooCoo 678b22ec73 Image Trace: Flat Logo finds a bar two pixels wide and three pixels long in its own color
The check for colors the palette cannot explain counted a pixel of a
bar two pixels wide only when its neighbors on both sides along the
bar were bar pixels. In a bar three pixels long only the two pixels of
its middle row passed, so its color had fewer than the three pixels a
color needs and was not added to the palette. A pixel now counts when
it lies at either end or in the middle of three such pixels in a row,
so all six pixels of a 2 × 3 bar count.
2026-10-24 13:00:06 -07:00
dotCooCoo 00f000f831 Pencil: a path-continuation cursor shows over an end a straight line continues
With Shift or Alt held (Alt only while Alt key toggles to Smooth Tool
is off), a Pencil press on an end of a selected open path draws a
straight line that continues the path, but before the press the cursor
was the plain straight-line cursor. Draw straight lines with the Pencil
tool says a path-continuation icon appears on the Pencil over that end:
https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-straight-lines-with-the-pencil-tool.html
Over such an end the cursor is now PencilContinue, the straight-line
cursor's line leaving a small filled square.

The check is the one the press makes (Edit Selected Paths, the same
end lookup). Away from the ends, with Edit Selected Paths at 0 pixels,
with nothing selected and while the line is drawn, the cursor is as
before.

Docs: docs/target-app-parity.md and docs/ui-parity.md, where the
duplicate Pencil lines and rows left by earlier merges are cut to one.
2026-10-24 12:26:17 -07:00
aiongg 8bf090b253 Shortcuts: use the shared craft-keymap crate for the catalogue, overrides, dispatch and .kys import (#3119)
* Shortcuts: the keymap, overrides and dispatch come from craft-keymap

The editor's catalogue (commands, UI commands, panels, tools; one tool
group per tool; show/hide commands marked as toggles) builds a
craft-keymap Keymap. UiState::shortcut_overrides is a craft_keymap
Overrides stored through keymap::stored, which writes the same map of
entry key -> one string as before, with panel: keys and VectorCraft's
spellings, so earlier versions read the file; a value that is not text
drops that entry instead of every preference.

Key presses run through craft_keymap's Dispatcher with accept filters:
keys that type text go to the one-key text path, and while the Type
tool edits only command-key chords run. Held keys repeat. Conflicts
and assignment use the crate's conflicts and set_bindings. Extra Shift
no longer falls back to the chord without it.

* Shortcuts: .kys import matches and applies through craft-keymap

The review proposes targets with craft_keymap's namematch, and Apply
hands the ready rows to craft_keymap::plan as one set, each row's
target passed as the remembered answer for its entry. The later row
takes a chord, and commands outside the set lose the chords the set
gives. Rows with one name in one block keep their own targets.
The engine's own namematch module is removed.

* Shortcuts: Cmd+, beside other hits, web Mac platform, cached dialog view

- Pin craft-keymap at 362eb59f.
- Cmd+, opens Settings on a Mac after the frame's other shortcuts run, with the
  Mac read from egui's OS; drop settings_chord, which dispatch no longer used.
- The web build follows the browser's OS, so Ctrl stays Ctrl on a Mac.
- The Keyboard Shortcuts dialog keeps its keymap, row texts and conflict count
  between frames while the working set is unchanged.
- Every default shortcut text becomes its entry's binding (test).
- craft-keymap counts as a UI crate for the layer check.
- Translate "too many changed shortcuts"; the parse error reuses the existing
  "can't parse shortcut `{c}`" message.

* Shortcuts: the chords the operating system takes can't be assigned

The keymap reserves craft_keymap::reserved::system for its platform:
window switching and closing, and the virtual consoles off the Mac.
The Keyboard Shortcuts dialog and shortcuts.set refuse them with
"<chord> is reserved by the system", and a saved override on one keeps
the entry's default. No default shortcut uses a reserved chord.

* Shortcuts: craft-keymap at craft-libs 2eca9b3a

craft-keymap comes from storytold/craft-libs (craft-libs #164 and #186), at the rev craft-pdf
already uses. The web build keeps the browser's full platform, so Windows browsers get Windows'
reserved chords, and each platform caches its own default keymap.
2026-10-24 15:01:26 -04:00
dotCooCoo 7a4ec1e530 Threaded text: New Symbol on a frame no longer duplicates its text
New Symbol and Redefine Symbol replaced a frame of threaded text by a
symbol instance whose art holds a copy of the frame. The thread's
re-flow then added the frame's text to the story, as it does for a
frame that was deleted, so the other frames held that text a second
time while the instance drew it.

A frame that New Symbol or Redefine Symbol puts in the symbol now
leaves its thread with the text it shows, and the other frames keep
the text they hold, as with Create Outlines, Expand and Flatten
Transparency. When the thread's last frame goes into the symbol, the
text it has no room for goes after the text of the last frame that
stays, and the copy in the symbol's art holds only the text the frame
shows. When every frame of a thread goes into the symbol, the copy of
the last frame keeps all its text. Undo restores the thread.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 11:15:42 -07:00
Marco Arnulfo 7a38c39a96 Properties and Transform: X, Y, W and H of a compound path's member set only that path (fixes #4046) 2026-10-24 19:53:46 +02:00
Marco Arnulfo 83600d3335 Color management: loading the working RGB profile again under its name takes effect at once 2026-10-24 19:36:34 +02:00
dotCooCoo deba1d1f76 Type: a recovered document keeps continued paragraphs in threaded frames
File > Data Recovery read the copy without marking the threaded frames
whose first paragraph continues from the frame before, so those frames
drew the paragraph's first-line indent and list marker again until the
next edit re-flowed the thread. The restored copy now gets the same
marks as an opened file.
2026-10-24 10:18:10 -07:00
dotCooCoo 0dcb52c7cd Export for Screens: <filename> takes the saved document's file name
In a file name pattern, <filename> is documented as the document's name
without its extension, but it expanded to the File Info title whenever the
document had one, so a saved my-logo.vectorcraft titled "Logo: final
version" exported as Logo--final-version__Artboard 1.png. The Full
Document file and the .zip took the same title.

Export for Screens and the dialog's Example line now take <filename> from
the document's file, as Save names it (save.rs), and fall back to the
title only for a document that has not been saved.
2026-10-24 09:53:53 -07:00
Drew DibbleandClaude Opus 5.5 31b653b70f Renderer tests: the Arabic alternate-glyph tests take the font lock
`tests_alternate`'s Arabic tests look up installed fonts by name
(`FontDb::face`), which loads each one into the global database and so
changes its generation, without `tests_fontchange::adding_fonts()`.
When that lands in the first frame of
`tests_charstroke::character_stroke_outlines_are_kept_between_frames`,
the second frame lays the type out again and expands the stroke again,
and the test fails (3 runs in 30 on macOS, where Arial and Geeza Pro
are installed; where no Arabic font is, those tests return early and
nothing changes). They now hold the lock, and its documentation says
looking up an installed font counts as changing the fonts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 23:49:34 +07:00
dotCooCoo b998ae623c MCP: save_file takes the native save options compress, version and preview
Squashed from #3955 onto main.
2026-10-24 23:43:54 +07:00
dotCooCoo 7a9ae4324d Threaded text: Rasterize on a frame no longer duplicates its text
Object › Rasterize replaced a frame of threaded text by an image of
it. The thread's re-flow then added the frame's text to the story, as
it does for a frame that was deleted, so the other frames held that
text a second time while the image drew it.

A frame that Rasterize turns into an image now leaves its thread with
the text it shows, and the other frames keep the text they hold, as
with Create Outlines and Expand. When the thread's last frame is
rasterized, the text it has no room for goes after the text of the
last frame that stays. A thread left with one frame is no longer a
thread, and undo restores the thread.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 09:37:10 -07:00
NetterSilverandClaude Haiku 5.5 a579ca7073 Require two hex digits in SVG href percent escapes
Found while testing crates/svg/src/import/files.rs:82 (percent_decoded)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 23:32:52 +07:00
NetterSilverandClaude Haiku 5.5 bd96665f44 Reject non-digit array indexes in batch step references
Found while testing crates/engine/src/steps.rs:74 (parse)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 23:26:50 +07:00
dotCooCoo 62a9c5e240 Shape Builder: uses a Live Paint group's filled faces and stroked edges
With a Live Paint group in the selection, the Shape Builder took every
visible path in the group as an input. Unfilled faces became regions of
their own and came back from a merge as invisible paths. An edge that
closes on itself, such as the outline of a single rectangle, became a
second shape over its face: a click on the face gave the merged shape
the edge's look, a stroke with no fill.

The Shape Builder now takes the group's visible filled faces as shapes
and its visible stroked edges as open lines; a closed edge is opened at
its first anchor. A merge replaces the group with the merged shape, the
filled faces it did not use and the edges, except the edges between
merged faces, as it does for other selected paths and lines. An area
that the edges enclose around an unfilled face is still a region. The
Live Paint commands still collect every face and edge.
2026-10-24 23:26:49 +07:00
dotCooCoo 0921b8629e Shape Builder: hover and Alt hover share one region map
Holding Alt over a selection the Shape Builder had already mapped built
the planar map a second time, to add the outline edges that erasing
hits. A merge, and a Live Paint Bucket fill of a plain selection, also
dropped the map, so a gesture that left the document unchanged was
followed by another full build.

The Shape Builder now builds its map with the outline edges the first
time, and both tools keep the map until the document, its revision or
the selection changes.

When the inputs have more than 128 segments, a hover with the Shape
Builder or the Live Paint Bucket now builds the map on another thread
and highlights no region until it arrives, instead of holding up the
frame; the canvas checks for it each frame. A press waits for the map,
and the headless MCP host waits for it after a move. The web build has
no threads and builds the map at once. The Live Paint Selection tool's
hover no longer builds a map of the selection, which it did not use.
2026-10-24 23:15:51 +07:00
dotCooCoo ebd512d2a4 Threaded text: Flatten Transparency on a frame no longer duplicates its text
Object › Flatten Transparency replaced a frame of threaded text by its
outlines with Convert All Text to Outlines, and by flat regions or an
image where the frame was transparent. The thread's re-flow then added
the frame's text to the story, as it does for a frame that was deleted,
so the other frames held that text a second time while the outlines,
regions or image drew it.

A frame that Flatten Transparency outlines or flattens now leaves its
thread with the text it shows, and the other frames keep the text they
hold, as with Create Outlines and Expand. When the thread's last frame
is flattened, the text it has no room for goes after the text of the
last frame that stays. A thread left with one frame is no longer a
thread, and undo restores the thread.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 23:04:33 +07:00
dotCooCoo 01380ba669 Shape Builder: a line that ends inside a shape stays whole
A merge removed every piece of an open line that had a merged region on
both sides. The end of a line that stops inside a shape has the same
region on both sides, so a click or a drag on the shape, or deleting it
in erase mode, cut the line back to where it enters the shape.

A piece is now removed only when it runs between two different merged
or deleted regions. A line that crosses a shape still splits it, and
merging the two halves still removes the piece between them.
2026-10-24 22:33:33 +07:00
dotCooCoo 3b0f626a4f Shape Builder: a drag picks every region it crosses, however narrow
A drag tested points spaced by a step that grew with the selection (at
least 0.25 pt), and the tool's highlight tested points 2 screen pixels
apart. A region narrower than the step, such as a thin overlap, fell
between samples: it kept its piece of each source, the merge left strips
behind, or the drag failed with "no region under the pointer" while the
highlight showed the region. Zoomed out, the highlight could miss a
region the merge then took.

The drag now finds where each segment crosses a region outline and tests
the middle of every stretch between crossings, so every region it
crosses is picked. The tool's highlight and shapeBuilder.merge use the
same picking, and Shaper's scribble picks the faces it crosses the same
way. In erase mode the line pieces and edges are still found within the
tolerance, and a segment that crosses one also tests the crossing point.

Shaper's scribble found the strokes to erase at points at least 0.5 pt
apart, so with a tolerance below 1 pt it could pass over a stroke
without erasing it. It now also tests where the scribble crosses each
stroke and points tolerance / 2 apart.
2026-10-24 22:02:39 +07:00
dotCooCoo 0520e9da86 Type: threaded frames do not repeat a continued paragraph's first-line indent or list marker
A paragraph split between two threaded frames took its first-line
indent again at the top of the next frame, and a list paragraph drew
its bullet or number there again. Each frame is laid out on its own,
and its first paragraph kept every paragraph attribute there.

A text object now marks a frame whose first paragraph continues from
the frame before (first_para_continues, not saved). The thread re-flow
sets the mark where each frame's share starts inside a paragraph and
measures each share with it, and a restored document reads the marks
from its saved text. Area type and type on a path set the first line of
such a paragraph as a wrapped line: no first-line indent and no bullet
or number. The paragraph after it takes the next number. Release
Selection, Remove Threading, Convert To Point Type and copies of a
frame clear the mark, so the first paragraph there has its indent and
bullet or number again. When Create Outlines, Expand or Expand
Appearance takes frames out of a thread, a frame that stays keeps the
mark only while it is threaded and the frames before it end inside a
paragraph. A frame whose mark changes lays out its first line again,
and when it is still threaded, its thread re-flows. When the outlined
frame is the last one, the text it has no room for is measured as the
frame lays it out: without the first-line indent when it continues a
paragraph, and with its numbers counted on from the frames before it.
The frame before it now takes exactly the text the last frame has no
room for. Duplicates, copies dragged in the Layers panel, the copies
that Release to Layers (Build), Release Opacity Mask and Edit Opacity
Mask make, and Draw Inside's clipping copy then have their cached
bounds computed again, as paste already does for pasted type.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 21:43:44 +07:00
Brandon ThomasandClaude Opus 5.5 186a1b140e Tests: the colour-font PDF test counts only text objects, not the random file ID
pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters counted
every line containing Tj or TJ. The file ID in the trailer and the XMP is
base64 derived from the timestamp, so about one run in a hundred it spells
one of them and the test saw two extra lines (main CI, 5c1185dd). Only
lines that begin a text object count now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:38:42 -04:00
Brandon Thomas 7e805f7ffc Merge remote-tracking branch 'origin/main' into pr-3421 2026-10-24 10:22:26 -04:00
dotCooCoo efff645a8e Shape Builder: rebuild the region map for each document revision
The Shape Builder, Live Paint Bucket and Live Paint Selection keep the
selection's region map between pointer events, keyed on the document's
address and the selected ids. Every edit replaces the document, and after
an undo followed by an edit a later document can be allocated at the
address of the one the map was built from. The key then matched a map of
an earlier revision, so a drag across the art merged nothing and a Live
Paint Bucket click filled nothing.

The key now also holds the tool context's revision, which moves with every
edit, undo and selection change.
2026-10-24 21:21:37 +07:00
Brandon ThomasandClaude Opus 5.5 aabf48728e Pencil: the closing ring tests the point a release keeps
A Paintbrush sample dropped for being under a pixel from the last point
left the ring computed from the pointer, while the release keeps the last
point; the ring now follows that point (CodeRabbit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:21:37 +07:00
dotCooCoo c607d2b448 Pencil: the closing cursor shows while a stroke is drawn back near its start
A Pencil or Paintbrush stroke released within Close Paths When Ends Are
Within of its start closes, but the cursor gave no sign of it. While the
pointer is where a release would close the stroke, the cursor is now
PencilClose (PaintbrushClose for the Paintbrush), the crosshair with a
ring, as Draw straight lines with the Pencil tool describes:
https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-straight-lines-with-the-pencil-tool.html

The check is the one the release makes, on the end the stroke would
take (snapped for the Pencil). A straight line (Shift or Alt held at the
press), a stroke that continues a selected path, a stroke that redraws
one and a smoothing drag show no ring.

Docs: docs/target-app-parity.md, docs/ui-parity.md.
2026-10-24 21:21:37 +07:00
vangel76andClaude Opus 5.5 aa8adf0891 fix(scriptcheck): settle what variables hold without a loader too
Without a loader, check() scanned the script once, so a function using a
variable the global code assigns after it (`function g(){ f.write('x'); }
var f = new File('a');`) missed file.write, while the loader path found it.
The no-loader path now runs the same fix-point, keeping a scan that finds
nothing new as the report scan, so a script that includes nothing gets the
same report with and without a loader.

Fixes #4011

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:10:06 +07:00
NetterSilverandClaude Haiku 5.5 4e9cd60540 Escape spaces before x<hex> and a separator in XML ids
Found while testing crates/doc/src/xml_id.rs:32 (from_object_name), decoded by escaped() at :72

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 21:03:43 +07:00
Brandon Thomas 06116452bc Merge remote-tracking branch 'origin/main' into pr-3421
# Conflicts:
#	crates/ui-egui/src/i18n/sv.tsv
2026-10-24 10:03:14 -04:00
NetterSilver 472cd812f9 Keep escaped backslash literal before U+XXXX in DXF MTEXT
Squashed from #4000 onto main.
2026-10-24 20:54:42 +07:00
vangel76andClaude Sonnet 5.5 e57773d45f refactor(scriptcheck): split analyze.rs, share the token filter
Move the declaration pass, the significant-token helpers and the feature
recognisers into analyze/declared.rs, sig.rs and features.rs without
changing their code, and replace the duplicated line-break and directive
dispatch of scan and declarations with LineBreaks::read.

Part of #4014

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 20:53:54 +07:00
vangel76andClaude Sonnet 5.5 4d2b628b9e refactor(scriptcheck): feature and outside ids as enums
FeatureId and OutsideId replace the string ids the analyzer passed around.
One table each gives the id, the need and the report order, so
catalog::FEATURES and catalog::need() are gone. Feature.id and the engine
JSON are unchanged; OUTSIDE is generated from the same table.

Fixes #4013

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 20:53:54 +07:00
vangel76andClaude Sonnet 5.5 f27b1e74e6 perf(scriptcheck): lex each file once, fewer copies
Work out the unescaped text and the declared names of each file once and
share them between the scans, keep included files as bytes and decode
them on demand, check the cheap tokens before unquoting in the '=' case,
and stop allocating a String per recorded chain.

Fixes #4010
Fixes #4012

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 20:53:53 +07:00