Skip to content

Commit aaabeef

Browse files
olivermeyerclaude
andcommitted
feat(gui): registry-based page registration with frame injection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c71f171 commit aaabeef

6 files changed

Lines changed: 501 additions & 141 deletions

File tree

ATTRIBUTIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ SOFTWARE.
360360

361361
```
362362

363-
## aignostics-foundry-core (0.8.1) - MIT License
363+
## aignostics-foundry-core (0.8.2) - MIT License
364364

365365
🏭 Foundational infrastructure for Foundry components.
366366

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# 5. GUI page registration
2+
3+
Date: 2026-04-08
4+
5+
## Status
6+
7+
Accepted
8+
9+
## Context
10+
11+
Bridge feature modules (`bridge.home`, `bridge.proscia`, `bridge.hello_world`, `bridge.papi`)
12+
need to register NiceGUI pages with a frame that displays a consistent header, navigation
13+
sidebar, and health status bar. The frame function (`bridge.system.gui._frame.frame`) depends
14+
on `Service().health()` from `bridge.system._service`, making it inherently a system-level
15+
concern.
16+
17+
The previous implementation created `gui = GUINamespace(frame_func=frame)` in
18+
`bridge/system/gui/_gui.py` and exported it from `bridge.system`. Feature modules imported
19+
this singleton via `from bridge.system import gui`. This is a **module boundary violation**:
20+
feature modules should not depend on the orchestrating system module.
21+
22+
### Alternatives considered
23+
24+
**Option A — Deferred singleton in foundry-core**: Add `gui = GUINamespace()` to
25+
`aignostics_foundry_core.gui` and a `configure(frame_func=...)` method. Feature modules import
26+
`gui` from foundry-core; `bridge.system` calls `gui.configure(frame_func=frame)` at startup.
27+
Rejected: pollutes a generic library with a stateful singleton that must be mutated by the
28+
application layer. Complicates testing (global state to reset). Philosophically wrong: the
29+
singleton is bridge-specific and should not live in a general-purpose library.
30+
31+
**Option B — New `bridge.gui` module**: Create `bridge/src/bridge/gui/` as a neutral singleton
32+
home, independent of `bridge.system`. Feature modules import from `bridge.gui`.
33+
Rejected: adds a thin wrapper module whose only content is a singleton. The module boundary
34+
problem is merely moved, not eliminated. No architectural gain justifies the additional module.
35+
36+
**Option C (chosen) — Registry-based page decorators**: The standalone `page_*` decorators in
37+
foundry-core (`page_authenticated`, `page_public`, etc.) write to a module-level `_registry`
38+
instead of calling `@ui.page()` immediately. `gui_register_pages(frame_func=frame)` processes
39+
the registry after all `BasePageBuilder.register_pages()` calls, actualizing each entry with
40+
the correct `frame_func`. Feature modules import only from `aignostics_foundry_core.gui`.
41+
`bridge.system` provides the `frame_func` to `gui_run()`, which flows it down to
42+
`gui_register_pages`. The `gui` singleton in `bridge.system` is deleted entirely.
43+
44+
## Decision
45+
46+
Implement Option C. The standalone `page_*` decorators become pure registration decorators
47+
that record intent (path, title, access level, page function) to a module-level list. The
48+
`gui_register_pages(frame_func)` function actualizes all entries using the private
49+
`_actualize_*` functions. `GUINamespace` methods continue to call `_actualize_*` directly,
50+
bypassing the registry (preserving the existing opt-in, frame-at-construction-time API for
51+
any future consumers that prefer it).
52+
53+
`gui_run()` gains a `frame_func` parameter that is forwarded to `gui_register_pages`.
54+
`bridge.system._service` passes `frame_func=frame` when calling `gui_run()`.
55+
56+
## Consequences
57+
58+
**Easier**:
59+
- Feature modules have zero dependency on `bridge.system` for page registration.
60+
- Dependency graph is clean: feature modules → `aignostics_foundry_core.gui`; `bridge.system`
61+
orchestrates and provides the frame.
62+
- Adding a new page requires only `from aignostics_foundry_core.gui import page_authenticated`
63+
— no reference to any singleton or bridge.system.
64+
65+
**Harder / risks**:
66+
- Page registration is now a two-phase process (write to registry, then actualize). Code that
67+
calls `page_authenticated(path)(func)` and expects the route to be live immediately (without
68+
subsequently calling `gui_register_pages`) will silently not register the route.
69+
- `_registry` is module-level mutable state. Tests must call `clear_page_registry()` in
70+
teardown to avoid cross-test contamination.
71+
- `GUINamespace` now calls a different set of internal functions (`_actualize_*`) than the
72+
public `page_*` API, which is a maintenance surface to keep in sync.

src/aignostics_foundry_core/gui/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .auth import (
1111
GUINamespace,
12+
clear_page_registry,
1213
get_gui_user,
1314
gui,
1415
page_admin,
@@ -42,6 +43,7 @@
4243
"GUINamespace",
4344
"NavGroup",
4445
"NavItem",
46+
"clear_page_registry",
4547
"get_gui_user",
4648
"gui",
4749
"gui_get_nav_groups",

0 commit comments

Comments
 (0)