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.
- zu_patterns/__init__.py +53 -0
- zu_patterns/_match.py +80 -0
- zu_patterns/autocomplete.py +62 -0
- zu_patterns/cart_checkout.py +102 -0
- zu_patterns/cookie_banner.py +71 -0
- zu_patterns/login_form.py +87 -0
- zu_patterns/modal_dialog.py +64 -0
- zu_patterns/paginated_list.py +52 -0
- zu_patterns/rail.py +86 -0
- zu_patterns/recognizer.py +80 -0
- zu_patterns/reversibility.py +178 -0
- zu_patterns/search.py +778 -0
- zu_patterns/search_box.py +59 -0
- zu_patterns/sortable_table.py +59 -0
- zu_patterns-0.2.2.dist-info/METADATA +69 -0
- zu_patterns-0.2.2.dist-info/RECORD +18 -0
- zu_patterns-0.2.2.dist-info/WHEEL +4 -0
- zu_patterns-0.2.2.dist-info/entry_points.txt +9 -0
|
@@ -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,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
|