This commit is contained in:
2026-06-06 02:34:22 -04:00
parent 0ccbc11cc2
commit 0262d0089c

View File

@@ -1,14 +1,14 @@
# Addon Skeleton Specification # Addon Skeleton Specification
**Project:** kane-diagnostics **Project:** kane-diagnostics
**Version:** 2.0 **Version:** 3.0
**Prerequisite reading:** README.md, DSC-development-map.md, HANDOFF.md, CODING-GUIDELINES.md, INSTITUTIONAL-RELATIONSHIPS.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md **Prerequisite reading:** README.md, DSC-development-map.md, CODING-GUIDELINES.md, INSTITUTIONAL-RELATIONSHIPS.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md, ASSOCIATION-CHANNEL-MODEL.md
--- ---
## The Standard ## The Standard
This document defines the skeleton standard for all Kane Diagnostics Hubzilla addons — present and future. The three addons described below are the first implementations of the standard, not the definition of it. Any addon added to this project follows the same skeleton: same file layout, same Widget structure, same PDL pattern, same config template fields, same spool contract discipline, same coding constraints. This document defines the skeleton standard for all Kane Diagnostics Hubzilla addons — present and future. The three addons described below are the first implementations of the standard, not the definition of it. Any addon added to this project follows the same standard: same file layout, same Widget structure, same PDL pattern, same config template fields, same spool contract discipline, same coding constraints.
The standard changes only by deliberate revision of this document. A revised document increments the version number and is committed to the repo. The previous version is never deleted — it remains as the historical record of what was built under it. The standard changes only by deliberate revision of this document. A revised document increments the version number and is committed to the repo. The previous version is never deleted — it remains as the historical record of what was built under it.
@@ -16,6 +16,11 @@ If you are reading this document and the repo contains addon code, read the code
Nothing in this document should be built without the operator's input and confirmation at each step. Nothing in this document should be built without the operator's input and confirmation at each step.
**Version history:**
- v1.0 — initial skeleton standard, single `corpus_builder_group_id` config, single `{addon}.php` file
- v2.0 — Widget directory structure, four-tab institutional directory, `listings.json.template` with Core slots
- v3.0 — per-association config structure, split-file PHP pattern (`{addon}.php` + `{addon}_renderer.php` + `{addon}_spool.php`), `xchan` field in listings template
--- ---
## The Three Addons ## The Three Addons
@@ -24,15 +29,14 @@ Nothing in this document should be built without the operator's input and confir
**Purpose:** Present the ten structural preconditions of an HOA association. Public-facing — readable without authentication. This is the first thing a participant reads and the first thing an attorney reads. **Purpose:** Present the ten structural preconditions of an HOA association. Public-facing — readable without authentication. This is the first thing a participant reads and the first thing an attorney reads.
**Route:** `/vs01` **Route:** `/vs01/[association-slug]/[VS-code]`
**Access:** Public read. No authentication required to view. HOA_MEMBER standing required to submit a Vital Signs record for a specific association. **Access:** Public read. HOA_MEMBER standing (SASE Participant or Corpus Builder group) required to submit a VS record for a specific association.
**What it does:** **What it does:**
- Presents each of the ten Vital Signs in plain language - Presents each of the ten Vital Signs in plain language, one at a time per JSON schema
- Explains why each one matters to the homeowner - Provides verification source guidance for each Vital Sign
- Provides public record lookup guidance for each Vital Sign (Recorder of Deeds, Secretary of State, IDFPR, etc.) - Allows a verified participant to submit a VS record for their association
- Allows a verified participant to submit a Vital Signs record for their association
- Stores the record via the orchestrator spool - Stores the record via the orchestrator spool
**What it does not do:** **What it does not do:**
@@ -77,25 +81,27 @@ Nothing in this document should be built without the operator's input and confir
- Presents the scenario carousel — browse only, no selection required - Presents the scenario carousel — browse only, no selection required
- Accepts the participant's account in their own words - Accepts the participant's account in their own words
- Writes the submission to the orchestrator spool as a TMP record - Writes the submission to the orchestrator spool as a TMP record
- Provides an operator-only `/scn01/manage` interface for scenario management and TMP review - Provides an operator-only `/scn01/[association-slug]/manage` interface for TMP review
**What it does not do:** **What it does not do:**
- It does not present Vital Signs - It does not present Vital Signs
- It does not present case law - It does not present case law
- It does not manage the institutional directory - It does not manage the institutional directory
**Relationship to pilot:** **Relationship to pilot:**
The pilot `ds01` addon in `caselaw-document-access` is the functional prototype for this addon. The new `scn01` is a clean rebuild on the complete foundation — not a port of the pilot code. The pilot is reference, not source. The pilot `ds01` addon in `caselaw-document-access` is the functional prototype for this addon. The new `scn01` is a clean rebuild on the complete foundation — not a port of the pilot code. The pilot is reference, not source.
--- ---
## Skeleton File Structure ## Skeleton File Structure
Each addon skeleton follows this structure. Files marked `[placeholder]` contain the correct structure but no functional logic. Each addon skeleton follows this structure. The `{addon}.php` entry point, `{addon}_renderer.php`, and `{addon}_spool.php` are the three mandatory PHP files for any addon that renders JSON-driven forms and POSTs to the orchestrator.
``` ```
hubzilla/addon/{addon}/ hubzilla/addon/{addon}/
{addon}.php — main addon file: hooks, load/unload, content routing {addon}.php — entry point: hooks, routing, access state, config/listings loaders, CSRF
{addon}_renderer.php — form renderer: schema loader, all field type functions, flags, sources
{addon}_spool.php — POST handler: validation, envelope builder, orchestrator POST
{addon}.apd — app descriptor {addon}.apd — app descriptor
mod_{addon}.pdl — PDL layout: aside, content, right_aside mod_{addon}.pdl — PDL layout: aside, content, right_aside
config.json.template — all config fields documented, no secrets config.json.template — all config fields documented, no secrets
@@ -109,30 +115,59 @@ hubzilla/addon/{addon}/
{addon}.js — all behavior, no inline scripts anywhere {addon}.js — all behavior, no inline scripts anywhere
contracts/ contracts/
spool-v1.json — the spool envelope contract this addon produces spool-v1.json — the spool envelope contract this addon produces
vital-signs/ — vs01 only: ten VS JSON schema files
VS-RENDERER-SPEC.md — vs01 only: renderer contract document
README.md — one paragraph: what this addon does and does not do README.md — one paragraph: what this addon does and does not do
``` ```
**File size constraint:** No PHP file may exceed 500 lines. If the entry point approaches that limit, extract further — for example, a `{addon}_manage.php` for the operator TMP interface. The split point is always a single `require_once` in the entry point.
--- ---
## Skeleton Content Requirements ## Skeleton Content Requirements
### {addon}.php ### {addon}.php — Entry Point
Must contain, in order: Must contain, in order:
1. **File header** — addon name, description, version, min/max Hubzilla version 1. **File header** — addon name, description, version, min/max Hubzilla version
2. **Hook registration**`{addon}_load()` and `{addon}_unload()` with PDL and Widget hooks 2. **require_once** loads `{addon}_renderer.php` and `{addon}_spool.php`
3. **PDL loader**`{addon}_load_pdl()` following the established convention 3. **Hook registration**`{addon}_load()` and `{addon}_unload()` with PDL and Widget hooks
4. **Helper**`{addon}_h()` — the HTML escape function 4. **PDL loader**`{addon}_load_pdl()` following the established convention
5. **Access state**`{addon}_access_state()` — returns `'public'`, `'participant'`, or `'operator'` 5. **Helper**`{addon}_h()` — the HTML escape function
6. **Access wall**`{addon}_access_wall()` — plain language, SASE link 6. **Access state**`{addon}_access_state($association_slug)` — returns `'public'`, `'participant'`, `'professional'`, or `'operator'`
7. **Content router**`{addon}_content()`loads CSS/JS, checks access, routes by path and method 7. **Professional check**`{addon}_is_professional()`reads `listings.json` xchan field
8. **Config loader**`{addon}_load_config()` 8. **Access wall**`{addon}_access_wall($association_slug)` — plain language, SASE link
9. **Listings loader**`{addon}_load_listings()` — reads `listings.json`, returns structured array; fails visibly if file is missing or malformed 9. **Content router**`{addon}_content()` — loads CSS/JS, reads argv(1) for association slug, argv(2) for sub-route, routes by path and method
10. **CSRF token and verify** — standard pattern 10. **Render functions** — one per route: index, landing, form, manage (TMP review)
11. **Placeholder content functions** — one per route, each returning a `// TODO` string with a plain description of what goes here 11. **Config loader**`{addon}_load_config()`
12. **Listings loader**`{addon}_load_listings()`
13. **CSRF token and verify** — standard pattern
No functional logic in the skeleton. No database calls. No file I/O beyond config and listings loaders. No orchestrator calls. Those come after the skeleton is confirmed. ### {addon}_renderer.php — Form Renderer
Contains all functions that read JSON schemas and produce HTML. Must contain:
- Schema loader — reads all schema files from the appropriate directory, sorts by filename, skips malformed files with visible logger call
- Seven field type functions — `text`, `number`, `date`, `boolean`, `select`, `textarea`, `multiselect`
- Field dispatcher — routes to the correct type function, appends diagnostic flag
- Conditional display wrapper — `depends_on` rendered with `data-` attributes
- Diagnostic flag renderer — evaluates `flag_condition` server-side, three severity levels
- Compound condition banner renderer — placeholder returning empty string until orchestrator query implemented
- Cross-VS reference renderer — linked callout
- Verification sources panel — collapsible Bootstrap collapse
- Perspective note renderer — information-record distinction for specific VS codes
- Immutable public record check and read-only renderer
### {addon}_spool.php — POST Handler
Contains all functions that handle form submission. Must contain:
- POST handler — validates CSRF, determines perspective, collects fields
- Required field validator — returns array of error labels
- Spool envelope builder — assembles the contract-compliant JSON payload
- Orchestrator POST — curl call with node token header, returns true on success or error string
- Form re-render with submitted values — called on validation failure
### {addon}.apd ### {addon}.apd
@@ -176,11 +211,27 @@ Every field the addon reads from `config.json` must appear here with a descripti
"_note": "Copy to config.json. Do not commit config.json — it contains secrets and installation-specific values.", "_note": "Copy to config.json. Do not commit config.json — it contains secrets and installation-specific values.",
"receiver_url": "REPLACE — orchestrator receiver endpoint", "receiver_url": "REPLACE — orchestrator receiver endpoint",
"node_token": "REPLACE — shared secret for node-to-orchestrator auth", "node_token": "REPLACE — shared secret for node-to-orchestrator auth",
"corpus_builder_group_id": 0, "listings_file": "REPLACE — absolute path to listings.json on the host",
"listings_file": "REPLACE — absolute path to listings.json on the host" "directory_default_tab": "core",
"associations": {
"REPLACE-SLUG": {
"_note": "Key is the URL slug — lowercase, hyphens only, matches argv(1) in the route",
"name": "REPLACE — association legal name",
"address": "REPLACE — association street address",
"channel_id": 0,
"channel_address": "REPLACE — channel@node address",
"groups": {
"public": 0,
"sase_participant": 0,
"corpus_builder": 0
}
}
}
} }
``` ```
Multiple associations are registered as additional keyed entries in the `associations` object. No code change is required to add a new association. See ASSOCIATION-CHANNEL-MODEL.md for the full operator workflow.
### listings.json.template ### listings.json.template
The institutional directory schema. This file documents the structure. The live `listings.json` on the host is operator-managed and never committed to the repo. The institutional directory schema. This file documents the structure. The live `listings.json` on the host is operator-managed and never committed to the repo.
@@ -189,7 +240,7 @@ The Core tier always contains exactly three slots. They are permanent. They rend
```json ```json
{ {
"_note": "Copy to listings.json on the host. Do not commit listings.json — it contains entity names and approval records that are operator-managed.", "_note": "Copy to listings.json on the host. Do not commit listings.json.",
"_version": "1.0", "_version": "1.0",
"core": [ "core": [
{ {
@@ -238,12 +289,16 @@ Entry schema for Tier-I, Tier-II, and Other:
"name": "REPLACE — display name", "name": "REPLACE — display name",
"description": "REPLACE — one sentence describing this entity's practice", "description": "REPLACE — one sentence describing this entity's practice",
"url": "REPLACE — public URL", "url": "REPLACE — public URL",
"xchan": "REPLACE — Hubzilla xchan hash of this professional's channel, used for access control",
"qualifying_case": "REPLACE — Tier-II attorneys only: case citation from public record", "qualifying_case": "REPLACE — Tier-II attorneys only: case citation from public record",
"listed_date": "REPLACE — ISO date of listing", "listed_date": "REPLACE — ISO date of listing",
"approved_date": "REPLACE — ISO date of written approval" "approved_date": "REPLACE — ISO date of written approval",
"status": "REPLACE — active or pending"
} }
``` ```
The `xchan` field is how the addon identifies a professional visitor and grants the `professional` access state. The operator records the xchan hash when approving the listing. Without it, the listing displays in the directory but does not grant professional access.
### Widget/{Addon}.php ### Widget/{Addon}.php
The Widget renders the institutional directory in the left aside. This is its only job. The Widget renders the institutional directory in the left aside. This is its only job.
@@ -257,11 +312,11 @@ Must contain:
- `render_tier_tab($entries, $placeholder)` — renders entries if any exist; renders placeholder text if none - `render_tier_tab($entries, $placeholder)` — renders entries if any exist; renders placeholder text if none
- `h($value)` — local HTML escape method - `h($value)` — local HTML escape method
The Widget does not render navigation. It does not render a steps indicator. It does not render resource panels. Those patterns belong to the pilot and do not transfer. The Widget does not render navigation. It does not render a steps indicator. It does not render resource panels.
#### Tab structure #### Tab structure
Four horizontal Bootstrap nav-tabs across the top of the aside: Four horizontal Bootstrap nav-tabs:
| Tab | Label | Content | | Tab | Label | Content |
|-----|-------|---------| |-----|-------|---------|
@@ -270,83 +325,53 @@ Four horizontal Bootstrap nav-tabs across the top of the aside:
| Tier-II | Tier-II | Attorneys — qualification standard per INSTITUTIONAL-RELATIONSHIPS.md | | Tier-II | Tier-II | Attorneys — qualification standard per INSTITUTIONAL-RELATIONSHIPS.md |
| Other | Other | Civic organizations, consumer protection entities, oversight agencies | | Other | Other | Civic organizations, consumer protection entities, oversight agencies |
The default active tab is Core. The operator may specify a different default per addon in `config.json` via a `directory_default_tab` field — this field is listed in `config.json.template` with value `"core"`. Default active tab is Core. Operator may specify a different default per addon via `directory_default_tab` in `config.json`.
A pending Core slot renders:
```html
<div class="{addon}-directory-slot {addon}-slot-pending">
<div class="{addon}-slot-role">{role}</div>
<div class="{addon}-slot-description">{description}</div>
<div class="{addon}-slot-status">Invitation pending</div>
</div>
```
A populated Core slot renders the entity name, mark (if provided), and URL. It never renders until `status` is `"active"` in `listings.json`.
### contracts/spool-v1.json ### contracts/spool-v1.json
The JSON contract this addon produces and POSTs to the orchestrator. Must contain: The JSON contract this addon produces and POSTs to the orchestrator. Must document:
- `_header` block with all required fields documented - `_header` block all envelope fields with descriptions
- `_payload` block with the addon-specific fields - `_payload` block the addon-specific fields
- `_meta` block describing the contract version and purpose - `_meta` block contract version and purpose
This file is the contract. When the payload structure changes, the version increments and a new file is created (`spool-v2.json`). The old contract is never deleted — it documents what the orchestrator must remain able to consume. When the payload structure changes, a new `spool-v2.json` is created. The old contract is never deleted.
### {addon}.css
Skeleton contains:
- File header comment with addon name and version
- Section comments for each visual area: layout, header, content, aside, directory, directory-core, directory-tiers, manage
- No rules yet — just the section comments as placeholders
### {addon}.js
Skeleton contains:
- IIFE wrapper
- `initDirectory()` function — placeholder, `// TODO: Bootstrap tab init if needed beyond data-bs-toggle`
- One placeholder function per interactive area with a `// TODO` comment
- `init()` calling all functions
- DOMContentLoaded guard
The pilot's `initResourcePanels()` function does not transfer. The new addons have no resource panels.
--- ---
## Build Sequence ## Build Sequence
The operator will direct which addon to build first. The expected sequence based on the DSC development map is: The operator directs which addon to build first. The expected sequence is:
1. `vs01` — Vital Signs — because it is the entry point for both participants and attorneys 1. `vs01` — Vital Signs — entry point for both participants and attorneys
2. `dsc01` — DSC Categories — because it is the primary reference surface 2. `dsc01` — DSC Categories — primary reference surface
3. `scn01` — Scenarios — because it depends on both Vital Signs and DSC context being established 3. `scn01` — Scenarios — depends on Vital Signs and DSC context being established
**Each skeleton must be confirmed working — loading without errors, displaying the access wall or placeholder content correctly, rendering all four directory tabs including the three pending Core slots — before the next skeleton begins.** Each addon must be confirmed working before the next begins.
--- ---
## What the Operator Will Decide Before Each Skeleton Is Built ## What the Operator Will Decide Before Each Addon Is Built
Before writing any skeleton file, discuss and confirm with the operator: Before writing any file, confirm with the operator:
1. The exact route — the operator may have a different preference than what is proposed here 1. The exact route
2. The access model — which privacy group gates participant access for this addon 2. The access model — which privacy groups gate participant access for this addon
3. The default directory tab — which of the four tabs is active on first load for this addon's typical visitor 3. The default directory tab
4. The config fields — whether any addon-specific configuration is needed beyond the standard fields 4. Any addon-specific config fields beyond the standard fields
5. The spool contract — what the payload block contains for this addon's submissions 5. The spool contract — what the payload block contains for this addon's submissions
Do not assume these decisions are made because they appear in this document. This document proposes. The operator decides. This document proposes. The operator decides.
--- ---
## What Not To Do ## What Not To Do
- Do not port the pilot `ds01` code. Read it as reference for Hubzilla conventions. Write the skeleton fresh. - Do not port the pilot `ds01` code. Read it as reference. Write fresh.
- Do not add functional logic to the skeleton. Placeholders only. - Do not generate all three addons at once. One at a time, confirmed working before the next begins.
- Do not generate all three skeletons at once. One at a time, confirmed working before the next begins.
- Do not create files that already exist in the repo. Read the repo first. - Do not create files that already exist in the repo. Read the repo first.
- Do not hardcode any value that belongs in `config.json`. - Do not hardcode any value that belongs in `config.json`.
- Do not write inline styles or inline scripts. - Do not write inline styles or inline scripts.
- Do not exceed 500 lines in any PHP file. The skeleton should be well under that — if it is approaching 300 lines before any logic is added, something is wrong. - Do not exceed 500 lines in any PHP file.
- Do not build the Widget without all four directory tabs present and the three Core slots rendering. The directory is not a feature added after the skeleton is confirmed. It is part of what makes the skeleton complete. - Do not build the Widget without all four directory tabs and the three Core slots rendering.
- Do not populate Core slots with entity names. Pending state is the correct skeleton state. Names appear only after written approval is recorded by the operator. - Do not populate Core slots with entity names. Pending state is the correct state until written approval is recorded.
- Do not push from the host. Gitea is the SSOT.