Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/.system/specs/NL-SPEC-A7K4QX2M-nuguland-agent-onboarding/index.md

Raw
Rendered preview

id: NL-SPEC-A7K4QX2M type: spec title: Nuguland agent onboarding architecture

Nuguland agent onboarding architecture

Binding contract for feature-oriented Nuguland. Rollout order lives in NL-PLAN-C4V7TQ2X. Primary outcome: an agent that has never seen this shell onboards a complete feature from public contracts only. Host internals are never a prerequisite; if onboarding cannot proceed without them, that is a contract gap and gets recorded as one.

Problem

The shell is a single composition root with injected services, but wiring is central: one feature change fans out across host, bar, catalog, service wiring, and fixtures. The cost is not file length, it is change fanout — how many unrelated places must stay consistent for one feature. Agents also pay a context tax reading host internals before every edit, and dynamic stateJson() snapshots hide the internal API shape from tooling. Strict proof surfaces more failures than looser projects; that honesty stays.

Baseline

Verified against the pre-revamp tree:

Symptom Evidence
Flat tree ~90 QML/JS files at root
Central composition shell.qml 1366 lines, ~20 IpcHandler functions
Bar feature knowledge Bar.qml 845 lines
Central popup selection PopoutHost.qml: 13-case switch, 14 inline Components
Feature sizing in host SurfaceManager.qml: heightBudget, batteryHeight, per-feature empty heights
Descriptive-only catalog popoutCatalog.js authors no registration
Hidden contracts panels parse stateJson() into var panelState

Adopted and rejected conventions

Distilled from the Omarchy quattro comparison; Nuguland keeps its isolation and proof, adopts locality:

Adopted Rejected
Feature directories as unit of work Plugin marketplace, third-party distribution
Conventional entry points Dynamic capability facades
One registration site Runtime JSON manifests
Shared panel lifecycle by default Broad var service APIs
Separately testable model logic UI-owned native service integrations
Named per-feature tests Mixing service ownership with presentation

Architecture

flowchart TB
  root["shell.qml · startup composition, typed IPC"]
  registry["typed QML registry · one entry per feature"]
  host["host · SurfaceManager + PopoutHost"]
  subgraph featuredir ["features/name/"]
    svc["service"]
    model["model"]
    panel["panel"]
    ctrl["bar control"]
  end
  root --> registry
  registry -->|entry points, sizing data, fixture| host
  root -->|typed injection| svc
  host -->|typed injection| panel
  panel -->|reads state| svc
  host -->|declared placement| ctrl

Feature directory

Names are free, responsibilities are invariant:

features/<name>/
  Service.qml · Panel.qml · BarControl.qml · Model.js
  README.md (human overview, no binding rules)
  tests/ · fixtures/
  • service — native integrations, system state, subprocesses, typed properties, intent methods; never geometry or focus.
  • model — pure transformations: classification, filtering, sorting, formatting, normalization; testable without QML or compositor.
  • panel — presentation, local cursor and focus state, intent emission, and every content state: normal, empty, unavailable, disabled, pending, failure, long content.
  • bar control — feature-owned bar presentation emitting intent; the registry states where it participates in bar layout.

Registry

The single registration site: a typed QML registry, declarative entries, covered by qmllint. No runtime JSON manifests, no second schema.

An entry covers:

  • stable feature ID;
  • service, panel, and bar-control entry points;
  • preferred and minimum dimensions, empty-state heights, sizing flags;
  • the named isolated fixture;
  • for specialized surfaces (lock, privilege), the owning controller kind instead of the default popup lifecycle.

An entry never covers IPC handlers; they stay explicit and typed in shell.qml. Geometry: declarative sizing data lives in entries; generic computation stays in the host. SurfaceManager keeps computation and loses per-feature cases; PopoutHost stops switching on feature names.

Host

Owns surface creation, screen selection, popup placement, lifecycle transitions, focus entry and return, outside dismissal, shared animation policy, and dependency injection. Contains no feature-specific rendering and no feature service logic.

Dependency and activation rules

  • Dependencies cross boundaries as explicit typed required property injection. No service registry, no singleton imports.
  • Features are selected at startup by checked-in composition. No runtime activation, no user-facing feature configuration.

Artifact rules

  1. One long-lived shell process per session.
  2. Services outside per-screen variants unless duplication is intended.
  3. PanelWindow for layer-shell surfaces, FloatingWindow for ordinary windows.
  4. Typed required property injection across component boundaries.
  5. One mutable state owner per concern; one geometry owner per axis.
  6. Shared controls and theme tokens.
  7. Pure logic outside QML where possible.
  8. JSON at IPC and diagnostics edges only.
  9. Loaders for expensive or rare surfaces; keepLoaded-style retention only when lifetime requires it.
  10. Event-driven service state over polling.
  11. Snapshot unstable native collections before feeding repeaters when required.
  12. Never childrenRect as a general sizing shortcut.
  13. Never weaken linting, isolation, or visual proof to make an iteration green.

Playbook

Every task starts with a bounded contract:

Feature · primary user action · observable result
State owner · geometry owner · service boundary · caller · surface host
States: normal | empty | unavailable | disabled | pending | failure | long content
Input: pointer | keyboard | focus entry | focus return | Escape | outside click
Proof: pure logic test | QML load test | interaction fixture | screenshots | full gate

Onboarding sequence: read RULES and the relevant spec; read the registry and one reference feature (audio); declare state and geometry ownership; implement and test pure model logic first; implement one complete interaction; register with one entry; run static and load checks; run the named fixture; inspect screenshots and logs; run the full gate.

Validation contract

Evidence levels stay separate and are defined in RULES. Per feature:

  • the registry names an isolated fixture that loads its panel with fixture dependencies;
  • changed behavior requires interaction and visual evidence, not only static and load proof;
  • the full repository gate stays green: zero-warning lint, complete JS suite, just ui-test.

Decisions

| # | branch | decision | | --- | --- | | 1 | primary outcome | new-feature onboarding | | 2 | edit budget | feature-local files plus exactly one explicit registration site | | 3 | surface scope | every surface kind, including future surface protocols | | 4 | protocol exceptions | lock/auth keep specialized controllers; popup host is default, not universal | | 5 | dependencies | explicit typed injection | | 6 | activation | startup composition only | | 7 | pilot | audio plus OSD | | 8 | state API | JSON-to-typed migration is a separate follow-up | | 9 | user configuration | none | | 10 | proof | a second agent onboards OSD independently against the frozen audio contract | | 11 | agent context | public contracts only | | 12 | layout | pilot physically moves audio and OSD into feature directories | | 13 | registry form | typed QML registry | | 14 | registry contents | service, panel, bar control, sizing data, fixture; never IPC | | 15 | abort criteria | owner judgement over recorded baseline metrics | | 16 | documentation | .system/ is authority; READMEs are human overview | | 17 | geometry | data in registry entries, computation in the host |

Acceptance criteria

  • A second agent onboards OSD from public contracts alone, without host internals.
  • One registry entry plus feature-local files is the entire wiring for an ordinary feature.
  • The owner judges the pilot evidence sufficient to continue.
  • One state owner per concern, safe service boundaries, deterministic isolated tests, zero-warning checks, real interaction evidence, and reviewed rendered output hold throughout.

Non-goals

Plugin marketplace; capability facades; new theme system; repository-wide rewrite; weakened sandbox; UI-owned native integrations; per-feature distribution; user configuration; runtime activation; runtime JSON manifests.

---
id: NL-SPEC-A7K4QX2M
type: spec
title: Nuguland agent onboarding architecture
---

# Nuguland agent onboarding architecture

Binding contract for feature-oriented Nuguland. Rollout order lives in `NL-PLAN-C4V7TQ2X`.
Primary outcome: an agent that has never seen this shell onboards a complete feature from public contracts only. Host internals are never a prerequisite; if onboarding cannot proceed without them, that is a contract gap and gets recorded as one.

## Problem

The shell is a single composition root with injected services, but wiring is central: one feature change fans out across host, bar, catalog, service wiring, and fixtures. The cost is not file length, it is change fanout — how many unrelated places must stay consistent for one feature. Agents also pay a context tax reading host internals before every edit, and dynamic `stateJson()` snapshots hide the internal API shape from tooling. Strict proof surfaces more failures than looser projects; that honesty stays.

## Baseline

Verified against the pre-revamp tree:

| Symptom | Evidence |
| --- | --- |
| Flat tree | ~90 QML/JS files at root |
| Central composition | `shell.qml` 1366 lines, ~20 `IpcHandler` functions |
| Bar feature knowledge | `Bar.qml` 845 lines |
| Central popup selection | `PopoutHost.qml`: 13-case switch, 14 inline `Component`s |
| Feature sizing in host | `SurfaceManager.qml`: `heightBudget`, `batteryHeight`, per-feature empty heights |
| Descriptive-only catalog | `popoutCatalog.js` authors no registration |
| Hidden contracts | panels parse `stateJson()` into `var panelState` |

## Adopted and rejected conventions

Distilled from the Omarchy `quattro` comparison; Nuguland keeps its isolation and proof, adopts locality:

| Adopted | Rejected |
| --- | --- |
| Feature directories as unit of work | Plugin marketplace, third-party distribution |
| Conventional entry points | Dynamic capability facades |
| One registration site | Runtime JSON manifests |
| Shared panel lifecycle by default | Broad `var` service APIs |
| Separately testable model logic | UI-owned native service integrations |
| Named per-feature tests | Mixing service ownership with presentation |

## Architecture

```mermaid
flowchart TB
  root["shell.qml · startup composition, typed IPC"]
  registry["typed QML registry · one entry per feature"]
  host["host · SurfaceManager + PopoutHost"]
  subgraph featuredir ["features/name/"]
    svc["service"]
    model["model"]
    panel["panel"]
    ctrl["bar control"]
  end
  root --> registry
  registry -->|entry points, sizing data, fixture| host
  root -->|typed injection| svc
  host -->|typed injection| panel
  panel -->|reads state| svc
  host -->|declared placement| ctrl
```

### Feature directory

Names are free, responsibilities are invariant:

```text
features/<name>/
  Service.qml · Panel.qml · BarControl.qml · Model.js
  README.md (human overview, no binding rules)
  tests/ · fixtures/
```

- **service** — native integrations, system state, subprocesses, typed properties, intent methods; never geometry or focus.
- **model** — pure transformations: classification, filtering, sorting, formatting, normalization; testable without QML or compositor.
- **panel** — presentation, local cursor and focus state, intent emission, and every content state: normal, empty, unavailable, disabled, pending, failure, long content.
- **bar control** — feature-owned bar presentation emitting intent; the registry states where it participates in bar layout.

### Registry

The single registration site: a typed QML registry, declarative entries, covered by `qmllint`. No runtime JSON manifests, no second schema.

An entry covers:

- stable feature ID;
- service, panel, and bar-control entry points;
- preferred and minimum dimensions, empty-state heights, sizing flags;
- the named isolated fixture;
- for specialized surfaces (lock, privilege), the owning controller kind instead of the default popup lifecycle.

An entry never covers IPC handlers; they stay explicit and typed in `shell.qml`.
Geometry: declarative sizing data lives in entries; generic computation stays in the host. `SurfaceManager` keeps computation and loses per-feature cases; `PopoutHost` stops switching on feature names.

### Host

Owns surface creation, screen selection, popup placement, lifecycle transitions, focus entry and return, outside dismissal, shared animation policy, and dependency injection. Contains no feature-specific rendering and no feature service logic.

### Dependency and activation rules

- Dependencies cross boundaries as explicit typed `required property` injection. No service registry, no singleton imports.
- Features are selected at startup by checked-in composition. No runtime activation, no user-facing feature configuration.

## Artifact rules

1. One long-lived shell process per session.
2. Services outside per-screen variants unless duplication is intended.
3. `PanelWindow` for layer-shell surfaces, `FloatingWindow` for ordinary windows.
4. Typed `required property` injection across component boundaries.
5. One mutable state owner per concern; one geometry owner per axis.
6. Shared controls and theme tokens.
7. Pure logic outside QML where possible.
8. JSON at IPC and diagnostics edges only.
9. Loaders for expensive or rare surfaces; `keepLoaded`-style retention only when lifetime requires it.
10. Event-driven service state over polling.
11. Snapshot unstable native collections before feeding repeaters when required.
12. Never `childrenRect` as a general sizing shortcut.
13. Never weaken linting, isolation, or visual proof to make an iteration green.

## Playbook

Every task starts with a bounded contract:

```text
Feature · primary user action · observable result
State owner · geometry owner · service boundary · caller · surface host
States: normal | empty | unavailable | disabled | pending | failure | long content
Input: pointer | keyboard | focus entry | focus return | Escape | outside click
Proof: pure logic test | QML load test | interaction fixture | screenshots | full gate
```

Onboarding sequence: read RULES and the relevant spec; read the registry and one reference feature (audio); declare state and geometry ownership; implement and test pure model logic first; implement one complete interaction; register with one entry; run static and load checks; run the named fixture; inspect screenshots and logs; run the full gate.

## Validation contract

Evidence levels stay separate and are defined in RULES. Per feature:

- the registry names an isolated fixture that loads its panel with fixture dependencies;
- changed behavior requires interaction and visual evidence, not only static and load proof;
- the full repository gate stays green: zero-warning lint, complete JS suite, `just ui-test`.

## Decisions

| # | branch | decision |
| --- | --- |
| 1 | primary outcome | new-feature onboarding |
| 2 | edit budget | feature-local files plus exactly one explicit registration site |
| 3 | surface scope | every surface kind, including future surface protocols |
| 4 | protocol exceptions | lock/auth keep specialized controllers; popup host is default, not universal |
| 5 | dependencies | explicit typed injection |
| 6 | activation | startup composition only |
| 7 | pilot | audio plus OSD |
| 8 | state API | JSON-to-typed migration is a separate follow-up |
| 9 | user configuration | none |
| 10 | proof | a second agent onboards OSD independently against the frozen audio contract |
| 11 | agent context | public contracts only |
| 12 | layout | pilot physically moves audio and OSD into feature directories |
| 13 | registry form | typed QML registry |
| 14 | registry contents | service, panel, bar control, sizing data, fixture; never IPC |
| 15 | abort criteria | owner judgement over recorded baseline metrics |
| 16 | documentation | `.system/` is authority; READMEs are human overview |
| 17 | geometry | data in registry entries, computation in the host |

## Acceptance criteria

- [ ] A second agent onboards OSD from public contracts alone, without host internals.
- [ ] One registry entry plus feature-local files is the entire wiring for an ordinary feature.
- [ ] The owner judges the pilot evidence sufficient to continue.
- [ ] One state owner per concern, safe service boundaries, deterministic isolated tests, zero-warning checks, real interaction evidence, and reviewed rendered output hold throughout.

## Non-goals

Plugin marketplace; capability facades; new theme system; repository-wide rewrite; weakened sandbox; UI-owned native integrations; per-feature distribution; user configuration; runtime activation; runtime JSON manifests.