the design doc moves into the plugin it produced

docs/offscale-plugin.md becomes plugins/offscale/PLUGIN.md. most of it is about
the plugin system generally rather than about offscale, which is exactly why it
belongs with the worked example — the reasoning is most useful beside the code
it produced, and the platform's docs should not carry the history of something
it no longer knows exists.

every reference updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 01:07:48 +00:00
co-authored by Claude Opus 5
parent b4dab16d2a
commit 4a9f23c759
4 changed files with 12 additions and 6 deletions
@@ -1,7 +1,12 @@
# Offscale — the first real plugin
**Status: LIVE DOCUMENT, opened 2026-08-14.** Decisions and findings from the session that started the
plugin system. Correct it in place; it is meant to be edited, not archived.
**Status: LIVE DOCUMENT, opened 2026-08-14, offscale extracted 2026-08-15.** Decisions and findings from
the session that built the plugin system. Correct it in place; it is meant to be edited, not archived.
It lives HERE, in the plugin, rather than in the platform's `docs/`. Most of it is about the plugin
system generally rather than about offscale, and that is deliberate: this is the worked example, and the
reasoning is most useful next to the code it produced. The platform's own docs should not carry the
history of something it no longer knows exists.
Offscale is Headscale extracted into a plugin. It is the pilot: chosen because it is a genuine vertical
slice (schema + backend router + sidecar + frontend screen + capabilities) without being pathological.
+1 -1
View File
@@ -27,7 +27,7 @@ export const manifest: PluginManifest = {
// `[open]` What a member's grant MEANS here is this plugin's own job and is not finished. The queries
// still scope by the caller (`listHeadscaleServers(userId)`), so a granted member would see their own
// empty server list rather than the owner's, and could register a Headscale of their own. The model in
// docs/offscale-plugin.md is one shared resource: read sees what the owner sees, write can change it.
// ./PLUGIN.md is one shared resource: read sees what the owner sees, write can change it.
// That is a change inside these queries, not a flag on the manifest.
//
// Worth knowing while it is unfinished: the stored credential is a Headscale ADMIN api key that can
+1 -1
View File
@@ -7,7 +7,7 @@ import { useServerEnvironment } from 'state/useServerEnvironment';
import { useInitialData } from '@/state/useInitialData';
// `installedPlugins`, not `plugins`: App.tsx already destructures a `plugins` from useServerSettings(),
// which is the DEAD plugin system — /server-settings/plugins scans src/workspaces/plugins/, a directory
// that does not exist, so it is always []. Different thing entirely; see docs/offscale-plugin.md.
// that does not exist, so it is always []. Different thing entirely; see plugins/offscale/PLUGIN.md.
import { plugins as installedPlugins } from './Plugins.gen';
export function App() {
+3 -2
View File
@@ -10,7 +10,8 @@
// the tile is `{ label, icon, color, to: mountPrefix() }` and the title is `label`, all of which are
// already below. Writing them twice could only ever drift.
//
// See docs/offscale-plugin.md for the reasoning behind each decision recorded here.
// The reasoning behind every decision here is recorded in plugins/offscale/PLUGIN.md — the worked
// example, kept next to the plugin it produced rather than in the platform's docs.
/**
* A permission the plugin adds to the platform's permission system.
@@ -34,7 +35,7 @@ export type PluginPermission = {
//
// Finer visibility — whose rows a member sees, what a read means for this plugin's data — is the
// PLUGIN's business and lives in its own queries. The platform's answer is uniform: read, write, or
// nothing. See docs/offscale-plugin.md.
// nothing. See plugins/offscale/PLUGIN.md.
/**
* Requests that look like writes and are not — `POST /ssh-test` probes, `POST /policy/assist` proposes
* a document and never saves one. Without declaring them, a read-level account meets what reads as a