Repository navigation
PDF Tools v2 follow-ups: local comment dates, Edit alignment and colour, real storage line, kept covers, Combine's footer - #479
Merged
Conversation
ymote
force-pushed
the
feat/pdftools-v2-followups
branch
from
October 11, 2026 07:59
3fec286 to
358afa8
Compare
ymote
added a commit
that referenced
this pull request
Oct 11, 2026
The user, from the live demo of #479: "side search and top search are duplicated". Search in document in the top bar is now the one way to search. The left rail keeps Pages, Outline and Comments, and has no Search. - Typing in the top field and pressing Return still opens the results in the left panel: "N matches on M pages", the list, previous and next, Done. No rail item is lit while they show. - A search remembers what the left side showed before its results took it (a panel, or the left side folded). Done, the results' cross (which only folded the panel before) or the field's cross ends the search and gives that back, Pages when it is unknown. It does so now, or when Comment or Edit gives the left side back. - A change the engine makes, which drops the stale matches, ends the search the same way. Before, it left an empty results panel that only the rail's Search led back to. - Nothing else opened the results: there were no shortcut buttons for them, and no find state survives a restart (library.json keeps the page and zoom, not the panel). Model tests cover the rail's items, which item is lit while results show, and the left side a search gives back. ui.py's shell journey checks the rail. Its light pass shows Done giving back the Comments panel. Its dark pass shows the results' cross folding the left side again. The README notes in English and Chinese that ⌘F's stand-in is the field itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
added a commit
that referenced
this pull request
Oct 11, 2026
Review of #479 (M2): pdf.comments read the whole document again on every call, on the UI thread (#399). Typical documents took 1 to 8 ms. It measured about 90 ms at 20k pages, about 230 ms with 100k comments, and about 100 ms for a 115 MiB file with a broken xref, which is rebuilt on every call. The dates read now stay on the open document, with a weak reference to the working bytes they came from. The engine swaps those bytes for new ones on every edit, undo, redo and save. The weak reference keeps their address from being reused, without keeping the bytes, so a kept read matches only the version it came from: right by construction. A second pdf.comments with no change reads nothing; the next one after a change reads once. Tests: - a second call reads nothing, and an edit, an undo, a redo and a save each read once (a test-only counter); - hostile dates (é where a date's digits or its zone go, and alone) are read without a panic: a zone that is not one is no zone, and text that is no date stays as written (review L4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
added a commit
that referenced
this pull request
Oct 11, 2026
Review of #479 (M1, L1–L4). The library learned a PDF's pages once and never again. Before #479 every start reopened each PDF; with kept covers, nothing refreshed them. So a 4-page PDF with a page deleted and saved said "4 pages" on Home after a restart, and Combine said "All (4 pages)". A range of "4" then passed the app's check, and the merge was refused. - A save records the open document's page count in the library, and marks the cover stale. - Once a start, Home asks pdf.cover for each PDF it shows. A kept cover answers from its file alone (a stat and a 24-byte read). A cover at a new path means the PDF changed, so its pages and size are read again from pdf.info (L2). - pdf.cover's too_large (a first page over 16 megapixels even at 48 dpi) no longer marks the PDF "Couldn't open this PDF" and drops it from Combine's and Insert's pickers. Like storage_full: no cover, pages and size from pdf.info (L1). - Home asks covers only for the first 32 PDFs it shows, as many as the service keeps. Asking for more evicted the first ones every start, so every start re-rendered everything. PDFs beyond those get their pages and size only (L3). ui.py's restart run now removes a PDF from Home and checks that its kept cover goes with it (L4). Before, the full run, which removes a PDF, had no covers to check. Both READMEs describe the covers as they are now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
added a commit
that referenced
this pull request
Oct 11, 2026
…nese Review of #479 (L4): - apps/AGENTS.md described the pdf service's render cache as .cache/pages/ alone. It now names pdf.cover, .cache/covers/, the 32-cover cap within the 16 MiB / 64-file cache, page renders going first, and comment dates read once per version of the document. - The Chinese files README's files.status row now lists the three older fields its English row has: photo_pick_supported, text_share_supported and max_share_text_bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
force-pushed
the
feat/pdftools-v2-followups
branch
from
October 11, 2026 08:00
358afa8 to
4806eb5
Compare
The engine lists a comment's /M through pdfcraft-render's pretty_date, which keeps the file's wall clock and drops the zone, and it stamps its own marks in UTC: a mark made at 17:32 on 10 Oct in California came back as 2026-10-11T00:32, labelled local, and PDF Tools showed "11 Oct". pdf.comments now reads each comment's and reply's /M as the file writes it, with the engine's own comment reader (pdfcraft-annot's summaries, the reader its listing comes from) over a pdfcraft-cos document opened on the open document's working bytes, and takes it to this device's local time from its zone (Z or +hh'mm'). The route only reads: the engine's copy of the bytes in memory, no file, no path, no script, one pass. A date with no zone is taken as written, as is the listed wall clock when the file's own date cannot be read or is not the one listed. The offset comes from where the shell's clock reads it (localtime_r's tm_gmtoff on Unix, chrono's Local elsewhere), at the date's own instant, and tests fix it. SERVICE.md's contract does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Design 08's Edit panel has Alignment (left, centre, right, justify) and Colour. pdfcraft's text_edit (at the pinned d801407a) formats a paragraph with both: align offsets each rewrapped line, and justify uses word spacing for every line but the last; color fills it in its own graphics state. pdf.edit_text takes optional align (left, center, right, justify) and color (#rrggbb, either case), with paragraph only. The service writes the colour it hands the engine from the three bytes it parsed, never one of the engine's colour names, and refuses anything else before the engine sees the call. The font, size, underline, spacing and move parameters stay excluded. The review is beside the method (src/change.rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDF Tools' Home rendered every cover again on each start: it opened each
PDF, rendered page 1 and closed it, and closing deletes a document's
renders, as a later open prunes them.
pdf.cover {path, dpi?} draws page 1 of a PDF in storage into the render
cache's .cache/covers/<name>-<size>-<modified>@<dpi>.png, all lowercase hex
and digits: <name> hashes the storage-relative name, so a PDF's covers
share an exact prefix no other PDF's carry, and a saved or replaced PDF
gets a new key while its older covers go. A kept cover answers from its
metadata and PNG header, with no engine work; a new one is a fresh
session with JavaScript off, like pdf.info, with no open document, no
handle and nothing counted toward the 8. Close, the isolate listener and
prune reach .cache/pages only.
Covers share the render cache's 16 MiB and 64 files, at most 32 of them.
Page renders go before covers when it needs room, and the whole cache is
cleared before a write would fail with storage_full:. dpi is 24 to 150
(default 48), at most 16 megapixels. The coordinator approved the
contract on 10 Oct 2026.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The contract changes decided for PDF Tools v2's follow-ups:
- Comments: a comment's and a reply's date is this device's local time,
converted from the zone the file writes it with (Z or +-hh'mm'); a date
written without a zone is taken as written.
- Edit: pdf.edit_text takes align (left, center, right, justify) and color
(#rrggbb), which format a paragraph only. It notes what the engine does
without them.
- Reading: pdf.cover {path, dpi?}, a PDF's first page kept across closes in
the render cache's .cache/covers, with its key, caps and errors. The
render cache's caps now count covers.
- Ground rules: the status line's storage use comes from the shell's
files.status (used_bytes, quota_bytes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDF Tools' status line should read "14.6 of 64 MB used" from the real numbers. It counted only its library's PDFs, against a constant. Splash has no storage-usage call (makepad's fs module: read, write, exists, remove, mkdir, list), and keeps the quota host-side. With live storage, files.status now also reports: - used_bytes: the host's measure of the app's jail, the root App Hub gives its isolates (app_storage::Storage::usage, which the engines' room checks use); - quota_bytes: the calling isolate's storage quota, the one its own writes and imports are checked against. Both are left out when either is unknown. Status still opens no dialog and needs no foreground. The discovery schema declares both. The README and the host-API status page say so in English and Chinese. Tests: - a child process seats a live isolate on the host's app storage: the jail's bytes, a write showing on the next status, the quota; - without live storage, neither is measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rage, kept covers
The app half of the v2 follow-ups, on the pdf service's new methods:
- Comment dates: the service now gives this device's local time, which the
thread cards already read as such ("Today 19:42", not "11 Oct" for a
mark made after 17:00 in California).
- Edit (design 08): Alignment, four segments (left, centre, right,
justify), with the one the paragraph's lines show chosen. The engine
sets an edited paragraph flush left unless told, so an edit always
names it. Colour is a box that opens a row of six colours; nothing is
sent until one is picked, since the engine does not report a
paragraph's colour. Apply sends pdf.edit_text {align, color?}; an
alignment or a colour alone is a change.
- Storage line: what the app's storage holds against what it may hold, from
files.status (used_bytes, quota_bytes), asked again a moment after each
burst of writes. A host that does not say falls back to the library's
PDFs against the system ceiling.
- Covers: Home asks pdf.cover for a PDF without a kept cover, and pdf.info
once for its pages and size, all kept in library.json. A start with
every cover kept asks the engine nothing. A save marks the PDF's cover
stale; Remove deletes it.
Also:
- The dev fixture answers pdf.cover and refuses edit_text formats as the
service does.
- Model tests cover the storage words, the alignment read from line boxes,
and what an edit sends.
- Both READMEs drop the two known issues these fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ort window With the desktop's window above the dock (1292 x 662, #478), Combine's own button sat 85 points below the fold with two PDFs listed. Now: - When the list fits, the card is as design 06 draws it. - When it doesn't, a tall card fills the pane. Its title and footer ("One PDF of N pages", Name and the Combine button) stay in view, and the file list scrolls inside it. - The tall card's list is a static scroll view, so it keeps its place while the list is drawn again (adding a PDF, typing its pages). - combine_list_h and combine_fits do the height math from the design's measures, and are model-tested. In the shell's default window (990 x 603): - The Edit panel draws its gaps tighter, so Apply and Cancel stay in view under Alignment and Colour. - Colour's box opens into its seven colours in its own row instead of a row below it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…footer ui.py's shell runs now also check what the follow-ups changed, with the real engine: - the status line against the storage's own bytes (files.status); - the new highlight and its reply dated "Today HH:MM" on this machine's clock (the engine stamps them in UTC); - the Edit panel's Alignment and Colour set before Apply (centred and red, then flush right in the paragraph's own colour); - Combine's own button in view with no scroll, with the list free to scroll inside the card; - one cover per PDF in the cover cache, each named in library.json, and every current cover kept untouched by a restart. What the runs saw goes to <output>/run.json for the receipt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d size With the storage full, pdf.cover answers storage_full. Home then asked nothing more, and the cards said "Reading…" for good. The old pipeline read a PDF's info before its render failed. A cover refused for room, or by a shell without pdf.cover, now still fetches the pages and size from pdf.info, which writes nothing. A damaged or locked PDF still says so on its card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Combine finds a PDF's page field in that PDF's own row, once the row sits still: a list scrolled by the wheel can still be moving, and a click could land on the row beside it. - The full-storage run waits until no card says "Reading…": each card shows its pages and size though there is no room for its cover. - The Edit step grabs the Colour box opened into its colours (08a-colours). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wn size ui.py's shell, restart, full and empty runs, rebased on main with #478. They ran in a hidden desktop shell with the real pdf engine, at the runtime's 64 ms budget, with PDF Tools at its own window size (an app area of 1292 x 662), light and dark. The fixture run used card-host at the designs' 1536 x 1024. What they show: - comment dates in this machine's local time ("Today 20:21" when UTC was 03:21 the next day); - the Edit panel's Alignment and Colour applied by the engine; - the status line from files.status ("187 KB of 64 MB used" for a storage of 191,793 bytes; the PDFs alone are 38,356); - one cover per PDF, kept untouched across a restart; - Combine's own button in view with two PDFs listed (before, 85 points below the fold); - with the storage full, cards that still show their pages and size. The receipt holds the digests, the commands and the numbers. The README's Verified, Not verified and Known say so in English and Chinese. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user, from the live demo of #479: "side search and top search are duplicated". Search in document in the top bar is now the one way to search. The left rail keeps Pages, Outline and Comments, and has no Search. - Typing in the top field and pressing Return still opens the results in the left panel: "N matches on M pages", the list, previous and next, Done. No rail item is lit while they show. - A search remembers what the left side showed before its results took it (a panel, or the left side folded). Done, the results' cross (which only folded the panel before) or the field's cross ends the search and gives that back, Pages when it is unknown. It does so now, or when Comment or Edit gives the left side back. - A change the engine makes, which drops the stale matches, ends the search the same way. Before, it left an empty results panel that only the rail's Search led back to. - Nothing else opened the results: there were no shortcut buttons for them, and no find state survives a restart (library.json keeps the page and zoom, not the panel). Model tests cover the rail's items, which item is lit while results show, and the left side a search gives back. ui.py's shell journey checks the rail. Its light pass shows Done giving back the Comments panel. Its dark pass shows the results' cross folding the left side again. The README notes in English and Chinese that ⌘F's stand-in is the field itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user: "there is no full screen read mode?". Design 02's floating bar ended with a full-screen button; we had built fit width and fit page instead. The bar now ends with it, and it opens Read mode: the pages alone. - Hidden: the document tabs, the mode bar, the rail and both panels, the tool row and the status line. Each hides through a plain wrapper, since a view whose on_render starts hidden stays hidden. - The pages get the whole window (canvas_size). The bar keeps previous and next, "3 / 24", the zoom, fit width, fit page, and "Exit read mode". - Scripts get no key events, so there is no Esc. A quiet, always-clickable control at the top right leaves too. - A tap on a page only chooses it: no tool's marks or editors show, and nothing is marked, filled or edited. - The page and the zoom the person chose carry across entering and leaving (kept_zoom). A fitted or automatic zoom fits the new canvas, and so does a resize while reading. - Both styles draw it from the theme's tokens. No app can make the OctoSense window itself full screen; there is no host API for it. Both READMEs say so under Known: maximize the window for the full effect. A "Read mode" entry in the Read tab's controls does not fit naturally (the Read tab has no control row), so the bar's button is the way in. Model tests cover which regions show, the canvas in and out of Read mode, and the zoom kept. ui.py's shell journey enters it from the bar, turns a page and zooms in, then leaves: by Exit read mode in the light pass, by the corner control in the dark pass. It checks the page and zoom carried over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #479 (M2): pdf.comments read the whole document again on every call, on the UI thread (#399). Typical documents took 1 to 8 ms. It measured about 90 ms at 20k pages, about 230 ms with 100k comments, and about 100 ms for a 115 MiB file with a broken xref, which is rebuilt on every call. The dates read now stay on the open document, with a weak reference to the working bytes they came from. The engine swaps those bytes for new ones on every edit, undo, redo and save. The weak reference keeps their address from being reused, without keeping the bytes, so a kept read matches only the version it came from: right by construction. A second pdf.comments with no change reads nothing; the next one after a change reads once. Tests: - a second call reads nothing, and an edit, an undo, a redo and a save each read once (a test-only counter); - hostile dates (é where a date's digits or its zone go, and alone) are read without a panic: a zone that is not one is no zone, and text that is no date stays as written (review L4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #479 (M1, L1–L4). The library learned a PDF's pages once and never again. Before #479 every start reopened each PDF; with kept covers, nothing refreshed them. So a 4-page PDF with a page deleted and saved said "4 pages" on Home after a restart, and Combine said "All (4 pages)". A range of "4" then passed the app's check, and the merge was refused. - A save records the open document's page count in the library, and marks the cover stale. - Once a start, Home asks pdf.cover for each PDF it shows. A kept cover answers from its file alone (a stat and a 24-byte read). A cover at a new path means the PDF changed, so its pages and size are read again from pdf.info (L2). - pdf.cover's too_large (a first page over 16 megapixels even at 48 dpi) no longer marks the PDF "Couldn't open this PDF" and drops it from Combine's and Insert's pickers. Like storage_full: no cover, pages and size from pdf.info (L1). - Home asks covers only for the first 32 PDFs it shows, as many as the service keeps. Asking for more evicted the first ones every start, so every start re-rendered everything. PDFs beyond those get their pages and size only (L3). ui.py's restart run now removes a PDF from Home and checks that its kept cover goes with it (L4). Before, the full run, which removes a PDF, had no covers to check. Both READMEs describe the covers as they are now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nese Review of #479 (L4): - apps/AGENTS.md described the pdf service's render cache as .cache/pages/ alone. It now names pdf.cover, .cache/covers/, the 32-cover cap within the 16 MiB / 64-file cache, page renders going first, and comment dates read once per version of the document. - The Chinese files README's files.status row now lists the three older fields its English row has: photo_pick_supported, text_share_supported and max_share_text_bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dark pass at --budget-ms 1000 lost the tap on Add files: the list of PDFs to add never opened, and the next step found no "Board minutes.pdf". The same step passed in that run's light pass and in every earlier dark pass. The driver now taps Add files until a PDF of the list shows, at most three times. It taps again only while none shows, so an open list is never tapped closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew's fixes ui.py's shell, restart, full and empty runs, in a hidden desktop shell with the real pdf engine, at the window's own size (an app area of 1292 x 662), light and dark, just after midnight on 11 Oct; and the fixture run in card-host at the designs' 1536 x 1024. The frames replace the first round's: - Read mode, entered on page 2 and left on page 3 at 125%, by the bar's Exit read mode (light) and by the corner control (dark). - Find from the top bar alone: the rail is Pages, Outline and Comments. Done gives the Comments panel back; the results' own cross folds the left side again. - Remove deletes a PDF's kept cover after a restart. - Comment dates read "Today 00:34" at 07:34 UTC. - Storage, covers, Combine's footer and Edit's Alignment and Colour, as before. The four runs used --budget-ms 1000. At the runtime's 64 ms, two attempts passed every functional step of the shell journey, light and dark, and then failed the log check on script budget overruns. Each overrun came with a UI-thread stall in a pdf service file rename: 555 ms in pdf.save, then 262 ms in the render cache. App Hub runs host services inline on the UI thread (#399), and this branch doesn't change those paths. The load average was 54 to 74 then. run-64ms/ keeps the third attempt's logs and the lines read from the second's before the third overwrote it. The README and receipt record this, and the first 1000 ms run, which lost a tap on Combine's Add files. The runs were made on 12e0e4cb, on main 07b63da. The branch was then rebased onto main bcd52cf, which brought in #484 alone (App Hub's pin moved from d86101d7 to c317f787). The runs were not repeated on it; setup.py --check --cargo, native_apps.py --check and the pdf service's tests pass on it. The app README's Verified and Known entries say the same, in English and Chinese. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
force-pushed
the
feat/pdftools-v2-followups
branch
from
October 11, 2026 13:53
4806eb5 to
177bdf1
Compare
ymote
marked this pull request as ready for review
October 11, 2026 14:07
Contributor
Author
|
Local CI passed on 177bdf1 ( GitHub would run for this PR: desktop.yml, phone.yml, apps.yml. Merged on this local pass (macOS runner queue); GitHub CI runs on the merge commit on main.
Skipped (these did NOT pass; (!) marks a skip that blocks tools/ci-local-merge.sh):
Notes:
Native GitHub acceptance passed on the same head: embedded-browser.yml. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The approved follow-ups to PDF Tools v2 (#473, on the pdf service v2 of #472), the two changes the user asked for after the demo (one search, Read mode), and the review's fixes. Rebased on main bcd52cf (#484).
What changes
/M (D:20261011003256Z)). Itscomment_listpasses/Mthrough pdfcraft-render'spretty_date, which drops the zone. So a mark made at 17:32 PDT on 10 Oct was listed as2026-10-11T00:32, labelled local.pdf.commentsnow reads each comment's and reply's/Mas the file writes it, through a reviewed read-only route. That route is the engine's own comment reader (pdfcraft_annot::summaries, the reader its listing comes from), run over apdfcraft_cosdocument opened on the open document's working bytes: no file, no path, no script, one pass.Zor±hh'mm') to this device's local time. The offset comes from where the shell's clock reads it:localtime_r'stm_gmtoffon Unix, chrono'sLocalelsewhere, at the date's own instant, injectable in tests.text_edit(at the pinned d801407a) formats a paragraph with both.pdf.edit_texttakes optionalalign(left,center,right,justify) andcolor(#rrggbb), withparagraphonly.files.statusnow reportsused_bytesandquota_bytes.used_bytesis the host's measure of the app's jail, the one the engines' room checks use.quota_bytesis the live isolate's quota.pdf.cover {path, dpi?}, approved on 10 Oct)..cache/covers/<name-hash>-<size>-<mtime>@<dpi>.png. Older covers of the same file are removed by an exact name-hash prefix.storage_full:.pdf.close, the isolate listener and prune reach.cache/pagesonly.align/color, thepdf.coverrow with caps and errors, and the storage ground rule.main.splash):files.status, refreshed after writes.pdf.cover'stoo_largeis handled likestorage_full: no cover, with pages and size frompdf.info. It no longer marks the PDF as unreadable.pdf.coverfor each PDF once a start. A cover at a new path means the PDF changed, so its pages and size are read again./Msafely. apps/AGENTS.md describes the covers, and the Chinese files README'sfiles.statusrow matches the English one.Verified (macOS, Apple silicon)
Before the rebase onto #484: head 12e0e4cb on main 07b63da. The shell for the runs was built from 94b6f044, whose tree differs from 12e0e4cb only in ui.py.
cargo test --locked -p octosense-pdf-service: 56 unit tests and thefirst_callandskilltests. Engine crate tests aren't in CI (CI never runs the twelve engine services' own tests (including their containment tests) #412).Z, a+02'00', a-04'00'and a zone-less date for comments and replies under fixed offsets, plus a mark made now, saved and reopened./Mis read safely.storage_full:, no open-document entry.qtywith JavaScript on and the drawing differs, while the cover equals the scripts-off render..cache/covers.cargo clippy --locked -p octosense-pdf-service --all-targets --no-deps -- -D warnings: clean.phone/,cargo test --locked --features mobile-apps -p octosense-shell: 1,185 passed.files_service: a child process seats a live isolate on real host storage and checksused_bytesandquota_bytes.pdftools_modeltests.python3 tools/setup.py --check --cargo,python3 tools/native_apps.py --check,check-shell-graph.shforoctosenseandoctosense-home, androm/tests/test_no_local_paths.py: pass. The desktop builds (app-hub,craft-engines).hub check --system-app: the same two findings as main's bundle (no store listing, unsigned). Nothing new.apps/pdftools/tests/ui.pyin a hidden desktop shell with the real engine, at PDF Tools' own window size (an app area of 1292 x 662). Theshell(light then dark),restart,fullandemptyruns all passed with--budget-ms 1000(see the next item). Evidence and receipt are inapps/pdftools/tests/evidence/v2-followups-20261010/.shellrun passed every functional step, light and dark, in two attempts. Each then failed ui.py's log check on script budget overruns, sorestart,fullandemptydidn't run.pdf.save, then 262 ms in the render cache. App Hub runs host services inline on the UI thread (Engine host services run inline on the UI thread under the SERVICES lock #399), and this PR doesn't change those paths.run-64ms/keeps the third attempt's logs, and the second attempt's error and hang lines, read before the third overwrote its log.fixturerun) at the designs' 1536 x 1024, light and dark: passed. That includes the find screen beside design 03, whose rail still draws a Search item.windowhint, so the scratch copy's manifest droppedwindow/schema_minor. The script is the branch's own.After the rebase onto bcd52cf, which brought in #484 alone (App Hub's pin moved from d86101d7 to c317f787):
python3 tools/setup.py --check --cargo,python3 tools/native_apps.py --checkand the pdf service's tests (56 + 1 + 1) pass.Not verified
restart,fullandemptyat the runtime's 64 ms in this round.Local) wasn't built here. Phones: the pdf engine is desktop-only.tools/ci-local.sh).Notes
pretty_dateslicesd[..12]before checking the bytes are digits. A crafted/Mwhose 12th byte falls inside a multi-byte character (/M (D:12345678901\351)) panics in inspection (inspect.rs:958, checked locally). The engine catches it, but the otherwise valid PDF then fails to open withdamaged: … the document has no pages. The service's own date code checks the bytes first.🤖 Generated with Claude Code