Skip to content

app-contract 1.12.0: optional window hint (schema_minor 1); card-host opens at it - #207

Merged
ymote merged 1 commit into
mainfrom
feat/manifest-window-hint
Oct 11, 2026
Merged

ymote merged 1 commit into
mainfrom
feat/manifest-window-hint

Conversation

@ymote

@ymote ymote commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

Why

An app could not ask for a larger desktop window. The manifest had no size field and refuses unknown keys, and the OctoSense desktop opens every app at about 72% × 76% of the desk, capped at 1000 × 720 points. PDF Tools v2 was designed at 1536 × 1024, so in that window its Combine button and its Home captions fall below the fold.

What changes

Contract (octosense-app-contract 1.12.0, unreleased)

  • New optional manifest field window: WindowHint { width, height } in points, built with WindowHint::new. It is the size the app prefers its desktop window to open at.
    • It is a hint, not a grant. Hosts clamp it to the screen, phones and other full-screen hosts ignore it, and the person can still resize the window.
    • AppPolicy does not change. A test checks that the resolved policy is the same with and without the hint.
  • SCHEMA_MINOR goes from 0 to 1, its first bump.
    • A manifest that sets window must declare "schema_minor": 1 or later. parse and resolve refuse it otherwise: window requires "schema_minor": 1 or later, so that hosts which predate window ignore it instead of refusing the app.
    • An older host (SCHEMA_MINOR 0) then reads the manifest leniently. It ignores window, lists it in ignored_fields(), and computes the same signing bytes, instead of refusing an unknown field.
  • parse checks the size: width 320 to 4096, height 240 to 4096.
    • Only an object is accepted. With serde's derived Deserialize, [1536, 1024] would also parse, and it would then serialize and sign as an object rather than as written. WindowHint therefore has a small hand-written Deserialize. It behaves like the derived one with deny_unknown_fields, except that it accepts only an object. It still names its fields to the lenient reader, so an unknown key inside window in a newer-minor manifest is ignored and listed like any other.
  • window is skipped when absent, so existing manifests and their signing bytes do not change.
    • The fixture corpus reproduces unchanged.
    • Two fixtures are appended: test-window-hint and refuse-window-without-minor.
  • Because of the bump, a manifest at schema_minor 1 is now read strictly, like any manifest at this build's minor. The lenient-path tests used minor 1 as "newer"; they now use SCHEMA_MINOR + 1.
  • The version is 1.12.0, with a CHANGELOG entry marked unreleased. No crates.io publication is claimed, and the README's publication section is unchanged.

card-host

  • Without --size, the window opens at the manifest's window size. It is sized exactly as with --size: no caption bar, and not clamped, because card-host is a render target. A second log line says that the size came from the manifest.
  • This lets App Flow's octo run capture an app at its design size.

Docs

  • docs/PUBLISHING.md and its zh-CN twin:
    • the manifest table gains a window row;
    • the schema_minor row now says to set 1 when the manifest uses window;
    • a new subsection, "The window size" (窗口尺寸), covers the range, the schema_minor rule, what hosts do with the hint, that it grants nothing, and how older hosts treat it.
  • docs/DEVELOPMENT.md and its zh-CN twin: the --size default and the window sized log line.

Validation

Ran:

  • The contract crate copied out of the workspace, as app-contract.yml does (macOS):

    • cargo test --all-targets: unit, fixture corpus and growth tests, 33 of them in growth;
    • cargo test --doc;
    • cargo clippy --all-targets --no-deps -- -D warnings;
    • cargo doc --no-deps with no warnings;
    • cargo package --list and cargo publish --dry-run.
  • The native-tools.yml steps on Linux, against plain sibling checkouts prepared by Octoscript-Makepad's tools/runtime.py prepare at the pinned revisions (makepad d653bc44, Octoscript-Makepad 6cf2d90a, Octoscript 2e37d9e6). All passed:

    • runtime.py verify --cargo-manifest, which runs cargo metadata --locked;
    • the contract, policy and hub tests;
    • the release build of card-host and hub;
    • the card-host CLI tests;
    • the appstore and app-hub-app lib tests with --no-default-features;
    • the card-host bin tests, including the new manifest-size test.
  • An older contract reading the new manifest. A scratch crate links the published 1.8.0 beside this crate. 1.8.0 has SCHEMA_MINOR 0, and its lenient.rs and parse_newer are byte-identical to 1.11.0's. Given a schema_minor: 1 manifest with window, 1.8.0:

    • accepts it;
    • lists ["window"] as ignored;
    • produces signing bytes identical to 1.12.0's, and the same resolved policy.

    Without schema_minor, 1.8.0 refuses unknown field window, and 1.12.0 refuses with the new message.

Not run locally (CI runs them):

  • cargo semver-checks, which is not installed here;
  • the macOS native_fonts.py step;
  • the Windows job.

The OctoSense side (repin, the shell opening a script app at its hint, PDF Tools asking for 1536 × 1024) follows in a separate OctoSense PR.

🤖 Generated with Claude Code

An app could not ask for a larger desktop window: the manifest had no size
field and refuses unknown keys. PDF Tools v2 was designed at 1536 x 1024,
and in the shell's default window its Combine button and Home captions fall
below the fold.

The manifest gains an optional `window` field, `WindowHint { width,
height }` in points: the size the app prefers its desktop window to open
at. It is a hint, not a grant. Hosts clamp it to the screen, phones and
other full-screen hosts ignore it, the person can still resize, and the
resolved AppPolicy does not change.

It is an optional field in the growth rule's sense, so SCHEMA_MINOR moves
to 1, its first bump. A manifest that sets `window` must declare
"schema_minor": 1 or later, and parse and resolve refuse it otherwise. A
host on an earlier contract then reads the manifest leniently, ignores
`window`, lists it among the ignored fields and computes the same signing
bytes, instead of refusing an unknown field (checked against the published
1.8.0, whose lenient path is unchanged through 1.11.0). Width must be 320 to
4096 and height 240 to 4096. Only an object is accepted: a derived
Deserialize would also take [w, h], which would then sign as an object
rather than as written. The field is skipped when absent, so existing
manifests and their signing bytes are unchanged; the fixture corpus still
reproduces, and gains test-window-hint and refuse-window-without-minor.

With the bump, a manifest at schema_minor 1 is read strictly like any at
this build's minor. The lenient-path tests now use SCHEMA_MINOR + 1, so
they keep testing the lenient path.

card-host: without --size, the window opens at the manifest's `window`
size, sized like --size (no caption bar, not clamped), so App Flow can
capture an app at its design size.

Docs: the manifest reference in PUBLISHING (en and zh-CN) gains `window`
and its rules, and `schema_minor` is no longer always left out; the
card-host --size default and its log line in DEVELOPMENT (en and zh-CN).
No crates.io publication is claimed: 1.12.0 is unreleased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote
ymote merged commit d86101d into main Oct 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant