diff --git a/ADDON-SKELETON-SPEC.md b/ADDON-SKELETON-SPEC.md index 470292a..406e474 100644 --- a/ADDON-SKELETON-SPEC.md +++ b/ADDON-SKELETON-SPEC.md @@ -1,14 +1,14 @@ # Addon Skeleton Specification **Project:** kane-diagnostics -**Version:** 2.0 -**Prerequisite reading:** README.md, DSC-development-map.md, HANDOFF.md, CODING-GUIDELINES.md, INSTITUTIONAL-RELATIONSHIPS.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md +**Version:** 3.0 +**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 -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. @@ -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. +**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 @@ -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. -**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:** -- Presents each of the ten Vital Signs in plain language -- Explains why each one matters to the homeowner -- Provides public record lookup guidance for each Vital Sign (Recorder of Deeds, Secretary of State, IDFPR, etc.) -- Allows a verified participant to submit a Vital Signs record for their association +- Presents each of the ten Vital Signs in plain language, one at a time per JSON schema +- Provides verification source guidance for each Vital Sign +- Allows a verified participant to submit a VS record for their association - Stores the record via the orchestrator spool **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 - Accepts the participant's account in their own words - 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:** - It does not present Vital Signs - It does not present case law - 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. --- ## 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}/ - {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 mod_{addon}.pdl — PDL layout: aside, content, right_aside config.json.template — all config fields documented, no secrets @@ -109,30 +115,59 @@ hubzilla/addon/{addon}/ {addon}.js — all behavior, no inline scripts anywhere contracts/ 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 ``` +**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 -### {addon}.php +### {addon}.php — Entry Point Must contain, in order: 1. **File header** — addon name, description, version, min/max Hubzilla version -2. **Hook registration** — `{addon}_load()` and `{addon}_unload()` with PDL and Widget hooks -3. **PDL loader** — `{addon}_load_pdl()` following the established convention -4. **Helper** — `{addon}_h()` — the HTML escape function -5. **Access state** — `{addon}_access_state()` — returns `'public'`, `'participant'`, or `'operator'` -6. **Access wall** — `{addon}_access_wall()` — plain language, SASE link -7. **Content router** — `{addon}_content()` — loads CSS/JS, checks access, routes by path and method -8. **Config loader** — `{addon}_load_config()` -9. **Listings loader** — `{addon}_load_listings()` — reads `listings.json`, returns structured array; fails visibly if file is missing or malformed -10. **CSRF token and verify** — standard pattern -11. **Placeholder content functions** — one per route, each returning a `// TODO` string with a plain description of what goes here +2. **require_once** — loads `{addon}_renderer.php` and `{addon}_spool.php` +3. **Hook registration** — `{addon}_load()` and `{addon}_unload()` with PDL and Widget hooks +4. **PDL loader** — `{addon}_load_pdl()` following the established convention +5. **Helper** — `{addon}_h()` — the HTML escape function +6. **Access state** — `{addon}_access_state($association_slug)` — returns `'public'`, `'participant'`, `'professional'`, or `'operator'` +7. **Professional check** — `{addon}_is_professional()` — reads `listings.json` xchan field +8. **Access wall** — `{addon}_access_wall($association_slug)` — plain language, SASE link +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. **Render functions** — one per route: index, landing, form, manage (TMP review) +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 @@ -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.", "receiver_url": "REPLACE — orchestrator receiver endpoint", "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 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 { - "_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", "core": [ { @@ -238,12 +289,16 @@ Entry schema for Tier-I, Tier-II, and Other: "name": "REPLACE — display name", "description": "REPLACE — one sentence describing this entity's practice", "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", "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 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 - `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 -Four horizontal Bootstrap nav-tabs across the top of the aside: +Four horizontal Bootstrap nav-tabs: | 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 | | 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"`. - -A pending Core slot renders: - -```html -
-
{role}
-
{description}
-
Invitation pending
-
-``` - -A populated Core slot renders the entity name, mark (if provided), and URL. It never renders until `status` is `"active"` in `listings.json`. +Default active tab is Core. Operator may specify a different default per addon via `directory_default_tab` in `config.json`. ### contracts/spool-v1.json -The JSON contract this addon produces and POSTs to the orchestrator. Must contain: -- `_header` block with all required fields documented -- `_payload` block with the addon-specific fields -- `_meta` block describing the contract version and purpose +The JSON contract this addon produces and POSTs to the orchestrator. Must document: +- `_header` block — all envelope fields with descriptions +- `_payload` block — the addon-specific fields +- `_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. - -### {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. +When the payload structure changes, a new `spool-v2.json` is created. The old contract is never deleted. --- ## 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 -2. `dsc01` — DSC Categories — because it is the primary reference surface -3. `scn01` — Scenarios — because it depends on both Vital Signs and DSC context being established +1. `vs01` — Vital Signs — entry point for both participants and attorneys +2. `dsc01` — DSC Categories — primary reference surface +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 -2. The access model — which privacy group gates 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 -4. The config fields — whether any addon-specific configuration is needed beyond the standard fields +1. The exact route +2. The access model — which privacy groups gate participant access for this addon +3. The default directory tab +4. Any addon-specific config fields beyond the standard fields 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 -- Do not port the pilot `ds01` code. Read it as reference for Hubzilla conventions. Write the skeleton fresh. -- Do not add functional logic to the skeleton. Placeholders only. -- Do not generate all three skeletons at once. One at a time, confirmed working before the next begins. +- Do not port the pilot `ds01` code. Read it as reference. Write fresh. +- Do not generate all three addons 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 hardcode any value that belongs in `config.json`. - 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 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 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 exceed 500 lines in any PHP file. +- 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 state until written approval is recorded. +- Do not push from the host. Gitea is the SSOT.