Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ArchiSimple Plugins

Distributable packages for ArchiSimple — one folder per package, each independently installable.

Nothing here is part of the application's build. These packages are written against the Extension SDK and loaded by the running application, which is the same path any third-party package takes.

archisimple-plugins/
  fr-urban-rules/         one package
    package.json          the manifest — the authoritative description
    src/index.js          the entry point, plain ESM JavaScript
    tests/                runs under `node --test`, no install, no build
  sh3f-importer/          another
  services/               companion services, run server-side (not packages)
  test-fixtures/          recorded answers the tests read (not a package)
  docs/                   design notes and extension ADRs

That shape is not incidental: it is the multi-package Development Repository layout the application supports. Add this folder once in Preferences → Repositories → Add local folder, and every subfolder carrying a package.json is discovered, installed and hot-reloaded as its own package.


Packages

Package Type What it does
fr-urban-rules/ extension Règles d'urbanisme (PLU): reads the planning rules on a French plot — regime, document in force, zone, prescriptions, easements, Géorisques risks and a link to the regulation — and hands the host a RegulationReport (SDK 1.28, regulations), the regulation opened at the zone's page where the deployment runs its companion service, which also reads the zone's written rules on request for the user to confirm; under the RNU and a carte communale, the coded national rules, the Site boundary's edges and the R.111-17 checks, with an edge-role correction written to the Site (document); drawn and entered rules to confirm; the emprise au sol and surface de plancher under their French definitions, the footprint budget, extension caps, a capacity estimate and indicative procedure flags; and the Space Programme warning (ai). The reference for contributing a regulation source and a planning-stage enricher.

urban-rules (Urban Rules, generic) was retired in Sprint 002.3: its programme warning moved to fr-urban-rules, read from the plot's confirmed rules rather than three typed preferences. An installed copy stays installed until its user removes it; see fr-urban-rules/README.md for the migration note. | sh3f-importer/ | extension | Imports Sweet Home 3D furniture libraries (.sh3f) into the Library — categories, metre dimensions, plan icons, doors and windows, and the licence each entry carries. The reference for contributing an importer, and the proof that a format needs no change to the application. |

Services

services/ (Sprint 002.5a) holds the server-side half of a package: Node programs the deployment runs in one plugin-services container behind a gateway, at /plugins-api/<package id>/ on the editor's origin (platform ADR-0114). They are not packages — never zipped, installed or loaded into the editor — and sit two levels down so neither the Development Repository nor tools/build-index.mjs mistakes them for one. The first is fr-urban-rules-service: it reads a plot's règlement and finds its zone's chapter. Node ≥ 22.13; node --test 'services/*/tests/*.test.mjs'.


Vocabulary

ArchiSimple distinguishes three things everyday speech calls "a plugin". Getting this right saves confusion later:

Term What it is Built here?
Package The distribution unit. A folder with a manifest; ships as a .aspkg archive. Yes — always
Extension A package that carries code: type: "extension" with an entryPoint. Yes — whenever there is behaviour
Plugin @archisimple/plugin-api — in-app registration, compiled into the application. No — lives in the application repo

So every folder here is a package; a folder with behaviour is an extension.


The one constraint that shapes everything

An extension developed here is loaded by reading its entry point's raw text and evaluating it as a module:

import('data:text/javascript;charset=utf-8,<your source>');

Standard dynamic import() of a data: URL. No bundler, no transpiler, no node_modules resolution. Two hard consequences:

  1. Write .js, not .ts. Plain ES module JavaScript, evaluated exactly as written. TypeScript here is a syntax error at load time.
  2. No bare specifier imports. import … from '@archisimple/extension-sdk' fails exactly as it would in a browser <script type="module"> — there is nothing to resolve it against.

Services are still reachable, because a service token is just { name: string } and the registry resolves by that name:

const ThemeServiceToken = { name: 'theme' };
const theme = context.services.get(ThemeServiceToken);

Well-known token names (from defineServiceToken(...) in the platform's packages/extension-sdk/src/services.ts, SDK 1.28): logging, document, selection, command, resource, theme, i18n, package, preferences, component-model, geo-map, map-features, terrain, vegetation, roads, site, regulations, site-package-data, country-profile, ai, assets, importer, exporter, sheet-exporter, and plugin-service (SDK 1.28, plugin-services.ts). context.commands and context.events arrive directly on the context, no lookup needed. The current SDK version is SDK_VERSION in packages/extension-sdk/src/sdk-version.ts (1.28 on 2026-10-20); CLAUDE.md has the table with notes.


The extension contract

export function activate(context) {
  const { services, commands, events, logger, subscriptions } = context;

  subscriptions.add(
    commands.register({
      id: 'my-plugin.checkSetback',
      titleKey: 'my-plugin.checkSetback.title',
      categoryKey: 'my-plugin.category',
      run: () => logger.info('checking setbacks')
    })
  );
}

export function deactivate() {}

Everything goes into subscriptions. The host disposes the store on unload, so teardown is clean even if deactivate is omitted or throws — that is what makes hot reload work without leaking commands and handlers.

Commands carry titleKey / categoryKey, never literal display text; the strings ship as i18n resources.

Capabilities

Declare exactly what you use. Read-only observation is ambient and needs nothing.

From EXTENSION_CAPABILITIES in the platform's packages/extension-sdk/src/capabilities.ts (SDK 1.28; no capability added since 1.25):

Capability Grants
commands context.commands.register()
events context.events.on()
document Document writes (transact) and your Site section's write
components Registering components and entity kinds
geo Contributing base-map layers
ai Contributing Skills and planning stages (ADR-0028)
assets Contributing Library asset definitions
import registerImporter() — listing importers is ambient
export registerExporter(), sheet exporters — listing is ambient
map-features Registering feature and road sources — listing is ambient
terrain Registering elevation providers
country-profile Registering a country profile
vegetation Registering canopy sources
regulations Registering a regulation source (SDK 1.25) — listing is ambient

rendering, properties, object-types and generators are reserved: valid in a manifest, but no service is gated on them yet. import and export were on that list until the importer (Sprint 041.1, ADR-0037) and exporter (Sprint 044.0, ADR-0041) services shipped — sh3f-importer declares import for exactly this reason, and without it its registerImporter call is dropped and reported.

Using an undeclared capability drops that call and reports it; the extension keeps running. Declaring an unknown one refuses the manifest outright, so a typo fails loudly at parse time.


Development loop

  1. Add the repository, once. ArchiSimple → Preferences → Repositories → Add local folder → pick this folder. Requires a Chromium-based browser (File System Access API). A local folder is code you wrote, so it needs no permission to run — that question is asked of remote repositories only (ADR-0059 Rule 4).
  2. Edit src/index.js and save. A poller notices, re-reads the manifest, reinstalls the package, deactivates the running extension and activates the new one.
  3. Watch the diagnostics. Preferences → Development Repository shows status, lastReloadAt and any message. Validate there is a dry run.
  4. Run the tests — no install, no build:
    node --test fr-urban-rules/tests/*.test.mjs
    fr-urban-rules/tests/acceptance.test.mjs (Sprint 002.4) replays the five reference plots from test-fixtures/fr-urban-rules/acceptance/<plot>/, captured in the editor by the developer command Capture test fixtures (developer), registered only when the preference fr-urban-rules.devCapture is true (see the package README); node tools/record-fixtures.mjs <plot> --trim keeps only what the tests read, and --check compares those recorded answers with the live services, for a person to read.
  5. Validate and ship:
    archisimple package validate ./fr-urban-rules
    archisimple package build    ./fr-urban-rules --out dist

build validates first and writes nothing if validation fails. An .aspkg is a ZIP with stored (uncompressed) entries and deterministic bytes — the same project always hashes the same.

The archisimple CLI comes from the application repo:

cd ../archisimple && pnpm build     # once
archisimple create package          # interactive manifest wizard

Publishing, so a deployment can install these

The development loop above needs a local folder and a Chromium file picker, so it reaches exactly one machine. A deployed ArchiSimple — a Docker image on a NAS, say — has no folder to point at, and with no importer installed File → Import library is correctly greyed out: nothing there can read a library.

The answer is a remote repository (ADR-0025). These plugins are built into .aspkg archives and published with an index.json; a user adds that URL under Preferences → Repositories and installs from it. ADR-0025's position is that "a Marketplace is simply another implementation of PackageRepository", so nothing about the install path is special — the pipeline that installs a bundled theme installs a downloaded one.

Installing is not running (ADR-0059, platform Sprint 058.0). That sentence about the install path is true of distribution and says nothing about execution: until 058.0 a downloaded extension installed correctly and was silently dropped at startup, because nothing evaluated its code. It now runs — when the user allows that repository to run code. The add-repository form asks, pre-ticked; a repository added before that existed starts as not allowed and has a control on its row. Somebody reporting "I installed it and nothing happened" on an older client is describing the bug this fixed; on a current one, check that checkbox first.

# Build every plugin here, and the index that advertises them.
node tools/build-index.mjs --cli ../archisimple/tools/cli/dist/archisimple.js

# Check the result with the platform's own parser, not a schema restated here.
node tools/check-index.mjs --platform ../archisimple

.github/workflows/publish-index.yml does both on every push to main and publishes dist/ to GitHub Pages.

It needs one secret. kefrens/archisimple is private, and a workflow's automatic GITHUB_TOKEN is scoped to the repository running it — so the platform checkout needs a token of its own:

Secret PLATFORM_REPO_TOKEN
Kind fine-grained personal access token
Access read-only Contents on kefrens/archisimple, nothing else

Read-only is enough: the workflow only reads the platform, and writes solely to this repository's Pages. Also enable Settings → Pages → Source: GitHub Actions here.

Without the secret the checkout fails with Not Found while retrieving the default branch — GitHub's answer for a private repository the caller cannot see, which reads like a misspelled repository name and is not. The workflow checks for the secret first and says so plainly.

The builder is not reimplemented here, and must not be. The archive format, its determinism and its SHA-256 belong to @archisimple/package-builder, reached through @archisimple/cli — which is why the workflow checks out the platform rather than installing something: both are private and unpublished. A second implementation of the .aspkg format would drift from the real one and surface as a checksum mismatch on a user's machine. The platform's Finding I-82 is four sprints lost to that mistake in a quieter place.

download in the index is relative, and the platform resolves it against the URL the index was fetched from. So the same dist/ works from GitHub Pages, from a release, or from any folder something serves — no build-time knowledge of where it will live.

Rules inherited from the platform

Not this repo's preferences — what the host enforces.

  • The SDK is the entire surface. An extension never imports an application package and never reaches internal state. There is no escape hatch, by design.
  • No hardcoded user-visible strings, and no hardcoded colours — read design tokens through the theme service.
  • Derive, don't duplicate. Never cache a value the model can compute.
  • Failure is isolated. A malformed manifest, an incompatible sdkVersion, a missing entry point or a throwing activate costs that package and nothing else. Don't write defensive scaffolding for this; it already exists.
  • Distribution and execution are separate. The Package Manager installs; the Extension Host runs. A package is valid or not independently of whether its code works — and being installed does not by itself mean being executed (ADR-0059): a remote repository runs code only once the user allows it.

Conventions

  • Folder name is the package's short name in kebab-case; the manifest id is the reverse-DNS form (com.archisimple.fr-urban-rules).
  • Command ids and translation keys are namespaced by package.
  • Each package is self-contained: no shared node_modules, no cross-folder imports, no build step. If two need the same helper, copy it — a package that cannot be zipped and installed on its own is not a package.
  • Conventional Commits, scoped by package: feat(fr-urban-rules): ….

Reference material

In the application repo at ../archisimple:

Path What it gives you
docs/sdk/00-getting-started/hot-reload.md Why plain JS and the token-name contract. Read this first.
docs/sdk/01-concepts/extension-sdk.md The contract, capabilities, versioning, isolation.
docs/sdk/03-cookbook/ Task recipes: add a command, a menu, a dialog, settings.
docs/guidelines/extension-guidelines.md The authoritative capability and isolation tables.
docs/guidelines/package-guidelines.md Field-by-field manifest table; what the builder checks.
examples/example-command-pack/src/index.js The shortest correct extension.
packages/extension-sdk/src/ Source of truth for tokens, events, capabilities, types.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages