zu-patterns 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ """search_box — a lone search/textbox (label/placeholder 'search') + optional submit.
2
+
3
+ Script: fill query, submit. Submitting a search is a GET ⇒ reversible-leaning.
4
+ Success: a results-list/listbox surface appears. Failure: no surface change.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from zu_core.invariants import Invariant
10
+ from zu_core.ports import PatternStep, RecognitionResult
11
+ from zu_core.surface import SurfaceView
12
+
13
+ from . import _match as m
14
+ from .rail import surface_shows
15
+
16
+
17
+ class SearchBox:
18
+ name = "search_box"
19
+ archetype = "search_box"
20
+
21
+ def recognize(self, surface: SurfaceView) -> RecognitionResult | None:
22
+ box = m.first(surface, roles=("searchbox",)) or m.first(
23
+ surface, roles=("textbox", "combobox"), tokens=m.SEARCH_TOKENS
24
+ )
25
+ if box is None:
26
+ return None
27
+ # A dedicated searchbox role is a strong signal; a textbox merely labelled
28
+ # 'search' is weaker. Don't fire if it looks like a login (a password
29
+ # field present) — that is login_form's territory.
30
+ if any(m.has_state(a, "password") for a in m.of_role(surface, "textbox")):
31
+ return None
32
+ submit = m.first(surface, roles=("button",), tokens=m.SEARCH_TOKENS + ("go",))
33
+ confidence = 0.85 if box.role.lower() == "searchbox" else 0.62
34
+ script = [
35
+ PatternStep(op="fill", role=box.role, label_hint=m.norm(box.label), note="query")
36
+ ]
37
+ if submit is not None:
38
+ script.append(
39
+ PatternStep(
40
+ op="submit", role="button", label_hint=m.norm(submit.label), note="search"
41
+ )
42
+ )
43
+ handles = tuple(h for h in (box.handle, submit.handle if submit else None) if h)
44
+ return RecognitionResult(
45
+ archetype=self.archetype,
46
+ confidence=confidence,
47
+ matched_handles=handles,
48
+ script=tuple(script),
49
+ detail="search box",
50
+ )
51
+
52
+ def success_invariants(self, result: RecognitionResult) -> list[Invariant]:
53
+ # Done = a results list/listbox surface EVENTUALLY appears (by the deadline).
54
+ return [surface_shows(self.archetype, "results_shown", label="results", liveness=True)]
55
+
56
+ def failure_invariants(self, result: RecognitionResult) -> list[Invariant]:
57
+ # Failure CONTEXT = an explicit "no results" empty-state appears. Safety
58
+ # shape: THROUGHOUT NOT contains(no results) — fires the instant it shows.
59
+ return [surface_shows(self.archetype, "no_results", label="no results", negate=True)]
@@ -0,0 +1,59 @@
1
+ """sortable_table — a table/grid with column-header buttons carrying sort states.
2
+
3
+ Script: click a header. Sorting is a view change ⇒ REVERSIBLE. Success: the sort
4
+ state toggles.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from zu_core.invariants import Invariant
10
+ from zu_core.ports import PatternStep, RecognitionResult
11
+ from zu_core.surface import SurfaceView
12
+
13
+ from . import _match as m
14
+ from .rail import surface_shows
15
+
16
+ _SORT_STATES = ("sort-asc", "sort-desc", "ascending", "descending", "sortable")
17
+
18
+
19
+ class SortableTable:
20
+ name = "sortable_table"
21
+ archetype = "sortable_table"
22
+
23
+ def recognize(self, surface: SurfaceView) -> RecognitionResult | None:
24
+ has_table = bool(m.of_role(surface, "table", "grid"))
25
+ # A sortable header is a columnheader/button affordance with a sort state.
26
+ header = next(
27
+ (
28
+ a
29
+ for a in surface.affordances
30
+ if a.role.lower() in {"columnheader", "button"} and m.has_state(a, *_SORT_STATES)
31
+ ),
32
+ None,
33
+ )
34
+ if header is None:
35
+ return None
36
+ confidence = 0.8 if has_table else 0.6
37
+ return RecognitionResult(
38
+ archetype=self.archetype,
39
+ confidence=confidence,
40
+ matched_handles=(header.handle,),
41
+ script=(
42
+ PatternStep(
43
+ op="click", role=header.role, label_hint=m.norm(header.label), note="sort"
44
+ ),
45
+ ),
46
+ detail="sortable table",
47
+ )
48
+
49
+ def success_invariants(self, result: RecognitionResult) -> list[Invariant]:
50
+ handle = result.matched_handles[0] if result.matched_handles else None
51
+ # Done = the same header is EVENTUALLY present again (the table re-rendered)
52
+ # — a minimal liveness-by-deadline check; a richer "state toggled" check is
53
+ # a later predicate. Asserted as presence of the header by the deadline.
54
+ return [surface_shows(self.archetype, "resorted", handle=handle, liveness=True)]
55
+
56
+ def failure_invariants(self, result: RecognitionResult) -> list[Invariant]:
57
+ # Failure CONTEXT = an error banner appears (the re-sort errored). Safety
58
+ # shape: THROUGHOUT NOT contains(error) — fires the instant it lands.
59
+ return [surface_shows(self.archetype, "sort_error", label="error", negate=True)]
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: zu-patterns
3
+ Version: 0.2.2
4
+ Summary: Zu pattern library: the policy-prior / move-ordering layer over the Action Surface (§5)
5
+ Project-URL: Homepage, https://github.com/k3-mt/zu
6
+ Project-URL: Repository, https://github.com/k3-mt/zu
7
+ License-Expression: Apache-2.0
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: zu-core==0.2.9
18
+ Description-Content-Type: text/markdown
19
+
20
+ # zu-patterns — the policy-prior / move-ordering layer (§5)
21
+
22
+ A UI is a state space; the **Action Surface** is the move generator (affordances
23
+ = legal moves). This package is the **policy prior + guided search** layer over
24
+ that surface — the *AlphaZero* shape, not Deep Blue. It does **not** brute-force a
25
+ live space (visiting a node might charge a card). It proposes the promising
26
+ interaction *without* exploring, and the rail (§1) verifies the prediction.
27
+
28
+ ## The new port — `Pattern` (group `zu.patterns`)
29
+
30
+ The `Pattern` Protocol lives in **zu-core** (`zu_core.ports.Pattern`), like the
31
+ other ports. A pattern is **read-only**: it `recognize`s a situation over a core
32
+ `SurfaceView` (`zu_core.surface`) and emits `success_invariants` /
33
+ `failure_invariants` (declarative `zu_core.invariants.Invariant`s the rail
34
+ verifies). It **never** calls a tool and **never** decides the task action — that
35
+ is the policy's job. A recognized pattern is a **prior to be confirmed by
36
+ observation, never ground truth** (ZU-RAIL-9): its success criteria compile (via
37
+ `compile_spec`) to Monitors, and a behaviour mismatch fires a detector.
38
+
39
+ The boundary that makes this clean: `recognize` takes the **core** `SurfaceView`,
40
+ never zu-tools' `Surface`. zu-tools projects its `Surface` onto `SurfaceView`
41
+ through a one-way adapter (`zu_tools.surface_adapter.to_surface_view`), so
42
+ zu-patterns depends only on zu-core.
43
+
44
+ ## The pieces
45
+
46
+ - `recognizer.py` — a pure pass over a `SurfaceView`: classify → archetype +
47
+ confidence. Low confidence ⇒ **no hint** (fall through to the model).
48
+ - `reversibility.py` — a principled, default-to-committing classifier of an
49
+ action as **reversible** (read-only/idempotent, safe to explore live) vs
50
+ **committing** (side-effecting — the live-search/rail commit boundary). No
51
+ site-specific keyword blocklist: HTTP-method/idempotency, affordance semantics,
52
+ an extensible prior set, default-to-committing on uncertainty.
53
+ - `rail.py` — the success/failure → `Invariant` helpers shared by the patterns.
54
+ - `search.py` — an offline best-first planner **over the Phase-1
55
+ `zu_core.reachability.Fsm`** with the recognizer as the move-ordering prior,
56
+ plus an event-log → `Fsm` transition-model builder. Pure, offline, $0. The
57
+ live guided-MPC loop and the Shadow-sourced transition model are **deferred
58
+ seams** (stubbed/documented).
59
+
60
+ ## The 8 starter archetypes
61
+
62
+ `cookie_banner`, `login_form`, `search_box`, `modal_dialog`, `paginated_list`,
63
+ `sortable_table`, `autocomplete`, `cart_checkout` — the last is the canonical
64
+ **irreversible-boundary** pattern (its place-order/pay step is classified
65
+ COMMITTING; the script stops before it).
66
+
67
+ All recognition is **deterministic** structural matching over roles/labels/states
68
+ (no model), so every pattern is tested at $0 with hand-built `SurfaceView`s. A
69
+ small-model recognizer is a future plugin behind the same Protocol.
@@ -0,0 +1,18 @@
1
+ zu_patterns/__init__.py,sha256=cY8Wn5TmR3gMvJ4fd7-aGqTuRKErQ-7F-vIOx_2h4pw,1141
2
+ zu_patterns/_match.py,sha256=n_It456Ta2J6ScxZOoul_ZRGdmziClxTzE3JPQgS1SY,3100
3
+ zu_patterns/autocomplete.py,sha256=KmbN59K2WvGyDG1RkiAVHs9bU25oiglNCHF15YVq-w4,2385
4
+ zu_patterns/cart_checkout.py,sha256=YZRkyV7I5g22AUsGu8pfDg9mq_qaRUhG4bdAtk_21fo,4745
5
+ zu_patterns/cookie_banner.py,sha256=PCF2QF77Z_51D6LTL5skV-qRbQvVQmzgBXSWxyOclfA,3269
6
+ zu_patterns/login_form.py,sha256=oHIYbatH0f4PPb9aRiYjZhilZe-4REhULEw8IG-PToA,3550
7
+ zu_patterns/modal_dialog.py,sha256=HR4TVu1yuv3dCg6euPPNg57ATHSZ72OKQ8RQ5W_GP74,3025
8
+ zu_patterns/paginated_list.py,sha256=pDrfu9NhgnesHh2mPKebvKTxt0ZUCeaRfHs1mud005g,2191
9
+ zu_patterns/rail.py,sha256=sprGCLvsIsufJK1supRlY-qGb1kGi7erg_7ZHE2Mrho,3853
10
+ zu_patterns/recognizer.py,sha256=KSR0ILYiNd41BV_s3EtNG7tU41RcPx33HnJ7yR-xTUk,3272
11
+ zu_patterns/reversibility.py,sha256=S7qIsfhO36TnNmh3GtFW8zYbkoaS4a5TvGsvb4jinUk,7076
12
+ zu_patterns/search.py,sha256=AbSe76B5vF94F8xJ_S1g1D_UBwsjyaEvhfLGippQW6E,32174
13
+ zu_patterns/search_box.py,sha256=NMmBdMbANLrvwHGQpNU4BHj1Sq3Xtqp-V0MaTx01jJE,2569
14
+ zu_patterns/sortable_table.py,sha256=vPZ49LlmPGjBUEWo_sam7t8CNdiGxXBjU865UCv3HAU,2358
15
+ zu_patterns-0.2.2.dist-info/METADATA,sha256=nJMhKDMsLto-t-1x9sgQqLOdXxuXTX7JYhqJQ8dZqjA,3699
16
+ zu_patterns-0.2.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
17
+ zu_patterns-0.2.2.dist-info/entry_points.txt,sha256=mqiuP7WrtcCysvxmohb8q6tpBewHvNruLJP2JXs4iVI,437
18
+ zu_patterns-0.2.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,9 @@
1
+ [zu.patterns]
2
+ autocomplete = zu_patterns.autocomplete:Autocomplete
3
+ cart_checkout = zu_patterns.cart_checkout:CartCheckout
4
+ cookie_banner = zu_patterns.cookie_banner:CookieBanner
5
+ login_form = zu_patterns.login_form:LoginForm
6
+ modal_dialog = zu_patterns.modal_dialog:ModalDialog
7
+ paginated_list = zu_patterns.paginated_list:PaginatedList
8
+ search_box = zu_patterns.search_box:SearchBox
9
+ sortable_table = zu_patterns.sortable_table:SortableTable