Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,57 @@ read that before making architectural decisions; this file only covers
what's needed to build and navigate the code day to day.

**Current state:** v1.0 released (phase 1.0 complete — the first full
release). Phase 0.5 (reliability and reach into the wider ecosystem)
release). Since then, the cooperative geometry port is real too (issue
#49, not yet in a tagged release): the escape hatch for gadgets nested
inside a `window.class` window's `layout.gadget`, permanently invisible
to structural walking on classic AmigaOS 3.x. A manifest gains a
version-2 record pair, `WHEREPORT <port-name>` and `WHEREGADGET
<logical-name> <window-name>`, naming a small optional ARexx port the
*application itself* exposes, answering `WHERE <name>` with its own
live `GetAttr(GA_Left/GA_Top/GA_Width/GA_Height)` geometry;
`CLICK`/`TYPE @name` then act on that geometry with a genuine
`input.device` click (`AmipClickWindowRelative()`,
`server/src/action.c`) exactly as they would for a plain `GADGET` name
-- discovery is cooperative, but actuation stays real input, unlike
`MUIREXX`, where the target's own port does the acting too. The
standalone `WHERE` verb (`server/src/where.c`) is a diagnostic probe:
`GETTEXT`/`DRAG` have no path through a `WHEREPORT` at all, an honest
stated limit (RC 10, "geometry only") rather than a silent fallback.
See `manifest/SPEC.md`'s "The cooperative geometry port" section for
the full wire contract, including its "Clash guard" note on why
`WHEREPORT` resolution is exact-match only (no `MUIREXX`-style `.1`
fallback) and why applications should use a dedicated port name.
Verified end to end against `fixtures/classact-app`'s own new
`CAAPP.WHERE` port (`tests/copperline/run.sh`'s `run_where_check`,
`tests/copperline/where-test.py`) -- all three of that fixture's
gadgets, previously named by nothing at all (its manifest deliberately
named zero gadgets as the honest example of this exact limit), are now
addressed purely via `WHEREGADGET`; `CAApp.golden` is unchanged,
confirming the walker's own view of the window genuinely didn't change.
A real bug found building this, live (2026-08-09): a `RexxMsg`
constructed by hand via `CreateRexxMsg()`/`FillRexxMsg()`/`PutMsg()` --
the same recipe `MUIREXX`'s own `AmipMuiRexxSend()` (`server/src/
muirexx.c`) already used successfully against real MUI-Demo -- arrives
at the receiver with its node type left at `NT_MESSAGE`, not
`NT_REPLYMSG`; `rexxsyslib.library`'s own `IsRexxMsg()` reports such a
message as not a genuine `RexxMsg` at all despite every other field
being correct. Two attempted sender-side fixes (pre-marking
`ln_Type = NT_REPLYMSG` before sending; using a genuine
`CreateArgstring()`-backed `rm_Args[0]`) changed nothing. Root cause:
`IsRexxMsg()` checks `ln_Type == NT_REPLYMSG`, which `ReplyMsg()`
sets -- the right check for a *sender* inspecting its own reply, not
a *receiver* validating an incoming, not-yet-answered command, which
is legitimately still `NT_MESSAGE` regardless of how the sender is
built. The field that actually means "this is a command invocation"
is `RXCOMM` itself (already set correctly by every sender here) --
fixed by having `CAAPP.WHERE` validate incoming messages with
`rm_Action & RXCOMM` instead of `IsRexxMsg()`, see the doc comment on
`HandleWhereMessage()` in `fixtures/classact-app/src/main.c`. A
genuinely standard, ordinary ARexx port -- nothing dedicated-port-only
about this fix, any third-party implementer (e.g. AmiAuth) needs only
the same one-condition change.

Phase 0.5 (reliability and reach into the wider ecosystem)
before it: `WAITFOR` (including its
`TEXT=` condition) and `CLICK`'s `EXPECT=` (wait/expectation
primitives, docs/implementation-plan.md's "Async by design" section)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ AREXX_SRC := $(ACTION_SRCDIR)/arexx.c $(ACTION_SRCDIR)/arexx_cmd.c \
$(ACTION_SRCDIR)/manifest.c $(ACTION_SRCDIR)/serial.c \
$(ACTION_SRCDIR)/tcp.c $(ACTION_SRCDIR)/fs.c \
$(ACTION_SRCDIR)/muirexx.c $(ACTION_SRCDIR)/wblaunch.c \
$(ACTION_SRCDIR)/screenshot.c
$(ACTION_SRCDIR)/screenshot.c $(ACTION_SRCDIR)/where.c
AMIPILOTD_SRCDIR := server/src/amipilotserver
AMIPILOTD_BIN := $(BUILD)/AmiPilotServer

Expand Down
32 changes: 21 additions & 11 deletions docs/implementation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ Three components:
marked in the API as the fragile tier — for genuinely custom-rendered
corners nothing structural can see, and nothing else.

**Future tier between 1 and 2 — the cooperative geometry port (design
note, not yet scheduled):** the layout.gadget child-enumeration limit
(see "Honest limits") blocks *external* discovery, but the application's
own code holds a live object pointer to every gadget it created — it
needs them for its own event dispatch. An app (or a GUI generator
emitting the app's boilerplate) can therefore expose a tiny optional
ARexx port answering `WHERE <logical-name>`: it calls
**A cooperative variant of tier 1, between it and tier 2 — the
cooperative geometry port (`WHERE`, issue #49, implemented):** the
layout.gadget child-enumeration limit (see "Honest limits") blocks
*external* discovery, but the application's own code holds a live
object pointer to every gadget it created — it needs them for its own
event dispatch. An app can therefore expose a tiny optional ARexx port
answering `WHERE <logical-name>`: it calls
`GetAttr(GA_Left/GA_Top/GA_Width/GA_Height)` on its own object pointers
and returns the live window-relative geometry. AmiPilot then acts on
that answer with a genuine input.device click at those coordinates —
Expand All @@ -122,10 +122,20 @@ real event path**, unlike the MUI tier where the port does the acting
too. No coordinates ever appear in a script (they're resolved live at
action time by the app itself), so relayout and font changes can't
break anything — the same immunity the manifest tier has, extended to
the one place structural walking can't reach. The manifest format would
gain a record declaring the port's name so consumers can discover it.
This is the designated escape hatch for ReAction/window.class UIs whose
gadgets are otherwise permanently invisible to tiers 1–2.
the one place structural walking can't reach. The manifest format
gained a version-2 record pair for this — `WHEREPORT <port-name>`
(declaring the port) and `WHEREGADGET <logical-name> <window-name>`
(a gadget resolved through it instead of `GA_ID`) — see
`manifest/SPEC.md`'s "The cooperative geometry port" section for the
full wire contract and `server/README.md`'s own WHERE entry for what's
verified live, including a real bug found building it (a hand-built
`RexxMsg`'s node type needing to match what a genuine ARexx
interpreter's own messages carry before `IsRexxMsg()` will recognise
it). This is the escape hatch for ReAction/window.class UIs whose
gadgets are otherwise permanently invisible to tiers 1–2 — for
applications that implement it; a third party still can't retrofit it
onto a binary that doesn't, the same honest boundary quirk profiles
already have.

Per-application **quirk profiles** (a small config layer) capture
app-specific mappings and known oddities, so community knowledge about
Expand Down
26 changes: 17 additions & 9 deletions fixtures/classact-app/CAApp.manifest
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
; AmiPilot manifest for the ClassAct/ReAction conformance fixture -- see
; manifest/SPEC.md for the format.
;
; Deliberately names NO gadgets: this fixture's button/string/checkbox
; are children of a window.class window's layout.gadget, which classic
; AmigaOS 3.x provides no public API to enumerate (see the project's
; documented "Confirmed limit") -- they are unreachable by GA_ID, and
; the manifest format has no way to express "unreachable but trust me"
; on purpose. This file exists as the honest example of that boundary:
; a manifest names exactly what a consumer can actually reach, nothing
; more.
MANIFEST 1
; This fixture's button/string/checkbox are children of a window.class
; window's layout.gadget, which classic AmigaOS 3.x provides no public
; API to enumerate (see the project's documented "Confirmed limit") --
; they are unreachable by GA_ID, and a plain GADGET record has no way
; to express "unreachable but trust me" on purpose. This is the format
; version 2 example of the honest escape hatch for exactly that
; boundary (issue #49): the fixture itself exposes a small, optional
; ARexx port ("CAAPP.WHERE", see fixtures/classact-app/src/main.c)
; answering WHERE queries with each gadget's own live geometry, and
; every gadget below is a WHEREGADGET resolved through it -- not a
; workaround for the walker's own limit, a real, live-queried answer
; to it.
MANIFEST 2
APP CAApp
WHEREPORT CAAPP.WHERE
WINDOW main "AmiPilot ClassAct Fixture"
WHEREGADGET connect_button main
WHEREGADGET host_field main
WHEREGADGET enabled_checkbox main
14 changes: 13 additions & 1 deletion fixtures/classact-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@

Hand-written ClassAct/ReAction test application -- phase 0.1's primary
`AmiInspect` fixture, later the server's click-and-assert conformance
target. Not yet implemented.
target. Built from `window.class`/`layout.gadget`/`button.gadget`/
`string.gadget`/`checkbox.gadget` (not GadTools, unlike
`fixtures/gadtools-app`) -- its three gadgets are all `layout.gadget`
children, the project's own documented "Confirmed limit": permanently
invisible to structural walking on classic AmigaOS 3.x.

Also implements the `CAAPP.WHERE` ARexx port (issue #49, `manifest/
SPEC.md`'s "The cooperative geometry port") -- the cooperative escape
hatch for exactly that limit. `CAApp.manifest` (format version 2)
addresses all three gadgets via `WHEREGADGET` records resolved through
this port, rather than the plain `GADGET` records it could never use.
See `src/main.c`'s own header comment and `tests/copperline/
where-test.py` for the end-to-end confirmation.
Loading
Loading