@smartkomm/plexus-cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +863 -2
- package/dist/cli.mjs +5432 -0
- package/dist/skill/.managed-by-plexus-cli +1 -0
- package/dist/skill/SKILL.md +224 -0
- package/dist/skill/references/accounts/email-changes.md +46 -0
- package/dist/skill/references/agents/use-shared-tools.md +52 -0
- package/dist/skill/references/applications/inspect-process-store.md +61 -0
- package/dist/skill/references/applications/start-a-test-application.md +66 -0
- package/dist/skill/references/audit/answer-access-request.md +115 -0
- package/dist/skill/references/config/attach-a-template.md +117 -0
- package/dist/skill/references/config/init-tenant.md +121 -0
- package/dist/skill/references/config/onboard-tenant.md +71 -0
- package/dist/skill/references/config/rename-program-stage.md +99 -0
- package/dist/skill/references/config/set-tenant-branding.md +150 -0
- package/dist/skill/references/config/set-tenant-terms.md +50 -0
- package/dist/skill/references/config/set-up-billing-catalog.md +73 -0
- package/dist/skill/references/config/update-tenant-config.md +174 -0
- package/dist/skill/references/config/wipe-tenant-config.md +107 -0
- package/dist/skill/references/export/create-named-export.md +47 -0
- package/dist/skill/references/feedback/create-and-triage-feedback.md +69 -0
- package/dist/skill/references/feedback/manage-feedback-tags.md +57 -0
- package/dist/skill/references/finance/change-contract-grant.md +36 -0
- package/dist/skill/references/finance/record-submission-date.md +33 -0
- package/dist/skill/references/interessenten/list-interessenten.md +54 -0
- package/dist/skill/references/marketing/demo-video-links.md +101 -0
- package/dist/skill/references/notifications/debug-mail-delivery.md +77 -0
- package/dist/skill/references/notifications/mail-level.md +90 -0
- package/dist/skill/references/notifications/reword-a-letter.md +155 -0
- package/dist/skill/references/notifications/trace-a-login.md +50 -0
- package/dist/skill/references/offer/add-workflow-node.md +268 -0
- package/dist/skill/references/offer/addresses.md +129 -0
- package/dist/skill/references/offer/appointment-task.md +79 -0
- package/dist/skill/references/offer/assignment-rule.md +195 -0
- package/dist/skill/references/offer/attach-form-to-workflow.md +79 -0
- package/dist/skill/references/offer/author-form-scenarios.md +97 -0
- package/dist/skill/references/offer/author-manifest-meta.md +94 -0
- package/dist/skill/references/offer/author-smartform.md +102 -0
- package/dist/skill/references/offer/bind-node-input.md +76 -0
- package/dist/skill/references/offer/completeness-check.md +137 -0
- package/dist/skill/references/offer/countersign-node.md +91 -0
- package/dist/skill/references/offer/create-pedagogy-unit.md +55 -0
- package/dist/skill/references/offer/declare-node-reads.md +99 -0
- package/dist/skill/references/offer/edit-live-tenant-offer.md +144 -0
- package/dist/skill/references/offer/edit-smartform.md +121 -0
- package/dist/skill/references/offer/edit-workflow-node.md +126 -0
- package/dist/skill/references/offer/engage-push-node.md +147 -0
- package/dist/skill/references/offer/insert-and-rewire-node.md +67 -0
- package/dist/skill/references/offer/node-tasks.md +162 -0
- package/dist/skill/references/offer/optional-assessment.md +65 -0
- package/dist/skill/references/offer/publish-offer-bundle.md +117 -0
- package/dist/skill/references/offer/set-offer-capacities.md +78 -0
- package/dist/skill/references/offer/staff-input-if-node.md +112 -0
- package/dist/skill/references/offer/task-appointment-result.md +69 -0
- package/dist/skill/references/offer/task-family-request.md +69 -0
- package/dist/skill/references/offer/task-result-files.md +52 -0
- package/dist/skill/references/offer/template-document-node.md +113 -0
- package/dist/skill/references/offer/workflow-default-channels.md +69 -0
- package/dist/skill/references/resolver/manage-contract-resolver.md +161 -0
- package/dist/skill/references/retention/erase-due-applications.md +99 -0
- package/dist/skill/references/retention/erase-due-parties.md +107 -0
- package/dist/skill/references/roles/migrate-parent-assignments.md +31 -0
- package/dist/skill/references/signing/README.md +113 -0
- package/dist/skill/references/signing/connect-a-school.md +90 -0
- package/dist/skill/references/signing/rotate-the-key.md +41 -0
- package/dist/skill/references/stammdaten/purge-deleted-person.md +68 -0
- package/dist/skill/references/stripe/connect-a-school.md +107 -0
- package/dist/skill/references/stripe/request-and-verify-a-payment.md +78 -0
- package/dist/skill/references/test-actions/drive-a-test-application.md +69 -0
- package/dist/skill/references/users/add-platform-admin.md +59 -0
- package/dist/skill/references/users/add-platform-user.md +59 -0
- package/dist/skill/references/users/add-tenant-admin.md +75 -0
- package/dist/skill/references/users/change-login-email.md +55 -0
- package/dist/skill/references/users/cli-access-for-tenant-admin.md +117 -0
- package/dist/skill/references/users/delete-permission-group.md +76 -0
- package/dist/skill/references/users/grant-cross-tenant-access.md +57 -0
- package/dist/skill/references/users/grant-feedback-assignable.md +43 -0
- package/dist/skill/references/users/set-password.md +57 -0
- package/dist/skill/references/users/use-test-account.md +79 -0
- package/dist/skill/references/worker-ops/debug-worker-jobs.md +90 -0
- package/dist/skill/references/worker-ops/verify-migrations.md +51 -0
- package/dist/spec.snapshot.json +156821 -0
- package/package.json +53 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Attach a blank template (Vorlage) to a form field
|
|
2
|
+
|
|
3
|
+
**When to use:** a school wants to offer a blank document on an upload field — a
|
|
4
|
+
reference form, a GDPR consent, a health record — that the family downloads,
|
|
5
|
+
fills in or signs, and uploads back at the same field. Or a template changed and
|
|
6
|
+
every form that offers it should show the new version.
|
|
7
|
+
|
|
8
|
+
The file lives in the tenant's **file library** (`tenant_files`, UI: Daten →
|
|
9
|
+
Dateien) under a stable **key**. A form field names the key only; label, file
|
|
10
|
+
name and type are read from the library. Replacing the file (same key) updates
|
|
11
|
+
every form at once — no republish.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- `plexus-cli` installed; a platform-admin token with `_platform.config.manage`
|
|
16
|
+
for the target env (listing needs `_platform.config.read`).
|
|
17
|
+
- The `files` commands and the `fileKey` attachment shape must be **live** on
|
|
18
|
+
the target env before an offer that uses them is published (a platform
|
|
19
|
+
operator rolls a release out to staging, then prod). An offer published before
|
|
20
|
+
that fails with `[config.attachment-source]` or `[form.unknown-attachment-file]`.
|
|
21
|
+
- Allowed types: PDF, DOCX, DOC, ODT, XLSX, JPG, PNG; max 20 MB.
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. **Put the file into the library** (create — a new key needs `--label` AND
|
|
26
|
+
`--label-en`: the label is shown in the reader's language):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
plexus-cli --env staging --tenant test files put referenzformular ./Referenzformular.pdf \
|
|
30
|
+
--label "Vorlage: Referenzformular" --label-en "Template: reference form"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`put` on an existing key **replaces** the file (keeps the key and the label
|
|
34
|
+
unless `--label` / `--label-en` is given). `--label-en` alone translates the
|
|
35
|
+
stored German; a German change over a real English is refused without
|
|
36
|
+
`--label-en`. Identical bytes and label are a no-op.
|
|
37
|
+
|
|
38
|
+
2. **Attach it to the field** in the offer bundle — a fresh export of the live
|
|
39
|
+
offer in a scratch directory of your own (production is the source of truth
|
|
40
|
+
for a real tenant's offer, so always start from a fresh export; see
|
|
41
|
+
[edit-live-tenant-offer](../offer/edit-live-tenant-offer.md)):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
WORK=$(mktemp -d) # a scratch directory
|
|
45
|
+
plexus-cli --env prod --tenant <slug> offer export --slug <offer-slug> --out $WORK/<offer-slug>
|
|
46
|
+
plexus-cli offer form field attach --dir $WORK/<offer-slug> \
|
|
47
|
+
--key kind.referenz --file-key referenzformular
|
|
48
|
+
# an inline link in the label instead of a download chip:
|
|
49
|
+
# --render inlineLink --label "Referenzformular"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
3. **Validate and publish** the bundle as usual (`offer validate`, `offer plan`,
|
|
53
|
+
`offer apply --publish`). Publish checks the key against the target tenant's
|
|
54
|
+
library.
|
|
55
|
+
|
|
56
|
+
## Declarative path (config-as-code)
|
|
57
|
+
|
|
58
|
+
The library is part of the tenant config (`files`):
|
|
59
|
+
|
|
60
|
+
```jsonc
|
|
61
|
+
"files": [
|
|
62
|
+
{
|
|
63
|
+
"key": "referenzformular",
|
|
64
|
+
"label": { "de": "Vorlage: Referenzformular" },
|
|
65
|
+
"filename": "Referenzformular.pdf",
|
|
66
|
+
"mimeType": "application/pdf",
|
|
67
|
+
"sha256": "…", // recomputed from `path` by the CLI
|
|
68
|
+
"sizeBytes": 48213, // recomputed from `path` by the CLI
|
|
69
|
+
"path": "files/referenzformular/Referenzformular.pdf"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `config export` downloads every file to `files/<key>/<filename>` beside
|
|
75
|
+
`config.json` and writes `path`. Swap a file on disk and `config apply --from
|
|
76
|
+
snapshot:…` uploads it (the file on disk decides the hash).
|
|
77
|
+
- `apply` uploads **only** files whose hash the target does not hold; a dry run
|
|
78
|
+
uploads nothing and lists them as `~ file files/<key>`.
|
|
79
|
+
- A file absent from `files` is reported as an **orphan** (`- orphan
|
|
80
|
+
foundation/tenant_files`) and removed only with `--allow-destructive` — then
|
|
81
|
+
still refused with `[file/inUse]` while any form version (archived included)
|
|
82
|
+
names it. A file uploaded on the Daten page is therefore never lost to an
|
|
83
|
+
older snapshot.
|
|
84
|
+
- A dry run also names changed files whose bytes the source cannot supply
|
|
85
|
+
(`NO bytes in this source`) — the commit would fail `[file/contentMissing]`.
|
|
86
|
+
- `--from env:<src>` downloads changed files from the source env and uploads them
|
|
87
|
+
to the target.
|
|
88
|
+
|
|
89
|
+
## Verify
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
plexus-cli --env staging --tenant <slug> files list # key, file, sha256, usedBy
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Open a draft application on the portal: the field shows a „Vorlage …" download
|
|
96
|
+
chip (or, for a document field, a „Vorlage" button inside the upload box). A
|
|
97
|
+
key the library no longer holds renders as **„Vorlage fehlt (<key>)"** — never
|
|
98
|
+
as a missing chip.
|
|
99
|
+
|
|
100
|
+
## Gotchas
|
|
101
|
+
|
|
102
|
+
- **Remove:** `plexus-cli files remove <key>` is refused while a form uses the
|
|
103
|
+
key; the error names `offer rN (status) / form / field` for each use. Detach
|
|
104
|
+
first (`offer form field detach --key … --file-key …`), republish, and note that
|
|
105
|
+
**archived** offer versions still count — applications pin them.
|
|
106
|
+
- Replacing or removing a file deletes its previous bytes in the same step (a
|
|
107
|
+
blank template has no audit value; the family's signed upload is the record).
|
|
108
|
+
Exception: a file the migration adopted from an old form attachment shares its
|
|
109
|
+
bytes with a document template (`ownsStorage: false`) — those go only once no
|
|
110
|
+
document template, resolver pin or template run of the tenant names them.
|
|
111
|
+
- The raw `storageId` attachment shape is retired; a stale bundle carrying one
|
|
112
|
+
fails publish with `[config.attachment-source]` naming the fix.
|
|
113
|
+
|
|
114
|
+
## Related
|
|
115
|
+
|
|
116
|
+
- UI: Daten → Dateien (upload, replace, delete).
|
|
117
|
+
- [Update a tenant's config](./update-tenant-config.md).
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Scaffold a brand-new tenant config from nothing
|
|
2
|
+
|
|
3
|
+
**When to use:** stand up a brand-new customer (tenant) from *nothing* — no snapshot,
|
|
4
|
+
no `.dev` export, no other environment to promote from. `config init` writes the
|
|
5
|
+
smallest valid config to disk; you then `config apply` it. If a config source already
|
|
6
|
+
exists (a `.dev` export / a tenant in another env), use
|
|
7
|
+
[Onboard / establish a tenant](./onboard-tenant.md) instead — that path skips the
|
|
8
|
+
scaffold.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
- **Env:** target with `--env <local|staging|prod>`. Tenant creation is implicit in
|
|
12
|
+
`config apply` (there is no separate "create tenant" verb). `config init` itself is
|
|
13
|
+
**offline** — no deployment, no auth.
|
|
14
|
+
- **Auth (for the apply, not the init):** a platform-admin token (`config apply` is
|
|
15
|
+
gated on `_platform.config.manage`). Confirm with `plexus-cli --env <env> whoami`.
|
|
16
|
+
The local admin-key fallback has no platform identity and is rejected — drive
|
|
17
|
+
`--env local` with a token too. A tenant admin's token cannot do this: a
|
|
18
|
+
tenant-scoped apply refuses a tenant that does not exist yet
|
|
19
|
+
([CLI access for a tenant admin](../users/cli-access-for-tenant-admin.md)).
|
|
20
|
+
- **Tooling:** an installed, current `plexus-cli` (`plexus-cli --help`).
|
|
21
|
+
- **Prod:** every writing run against prod needs `--allow-prod` (`config apply`,
|
|
22
|
+
`users provision`); without it the CLI refuses.
|
|
23
|
+
|
|
24
|
+
## Decide before you apply
|
|
25
|
+
|
|
26
|
+
Settle these with the school first — each one is a later playbook, but the answers
|
|
27
|
+
belong to onboarding:
|
|
28
|
+
|
|
29
|
+
| Decision | Default of the scaffold | Where it is set |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Slug + display name | — (`--tenant`, `--name`) | step 1; the slug is the identity, never rename it |
|
|
32
|
+
| Modules the school bought | **all** grantable module permissions | trim `tenant.permissions` in the scaffold before step 4 |
|
|
33
|
+
| „Bewerbung" or „Anmeldung" | Bewerbung (no `terms` in the scaffold) | [Set a school's terms](./set-tenant-terms.md) |
|
|
34
|
+
| Which mail families get | `all` („Alle E-Mails“) | [Mail level](../notifications/mail-level.md) |
|
|
35
|
+
| Portal branding (colors, logo, fonts) | neutral | [Set tenant branding](./set-tenant-branding.md) |
|
|
36
|
+
| Payments / e-signature | none | [Stripe](../stripe/connect-a-school.md) · [Signing](../signing/connect-a-school.md) |
|
|
37
|
+
|
|
38
|
+
**Look at an existing tenant as a reference.** Export one you can reach into a private
|
|
39
|
+
scratch dir and compare:
|
|
40
|
+
`plexus-cli --env prod config export --tenant <existing slug> --out <scratch>/ref.json`.
|
|
41
|
+
|
|
42
|
+
## Steps
|
|
43
|
+
```bash
|
|
44
|
+
# 1. Scaffold a minimal, valid tenant config (offline — no deployment, no auth).
|
|
45
|
+
plexus-cli config init --tenant <slug> --name "<Display Name>" \
|
|
46
|
+
--out config/.dev/<slug>/config.json # --out defaults to this path
|
|
47
|
+
|
|
48
|
+
# 2. (Optional) edit config/.dev/<slug>/config.json — add custom fields, roles,
|
|
49
|
+
# extra units/legal entities, a `source` block if this tenant imports data, etc.
|
|
50
|
+
# The scaffold has NO `source` block: a tenant that never imports needs none.
|
|
51
|
+
|
|
52
|
+
# 3. Dry-run the apply (shows the create/update plan; writes nothing).
|
|
53
|
+
plexus-cli --env <env> config apply --tenant <slug> \
|
|
54
|
+
--from snapshot:config/.dev/<slug>/config.json
|
|
55
|
+
|
|
56
|
+
# 4. Commit it.
|
|
57
|
+
plexus-cli --env <env> config apply --tenant <slug> \
|
|
58
|
+
--from snapshot:config/.dev/<slug>/config.json --confirm
|
|
59
|
+
|
|
60
|
+
# 5. Provision the generic admin login (separate auth plane — see related playbook).
|
|
61
|
+
# Convention: admin@<short>.plexus.swop.schule, name "<Short> Administration",
|
|
62
|
+
# so a platform operator always has one login per school that belongs to no person.
|
|
63
|
+
plexus-cli --env <env> --tenant <slug> users provision admin@<short>.plexus.swop.schule \
|
|
64
|
+
--name "<Short> Administration" --group tenant_admin --output creds.csv --quiet
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Write `config/.dev/…` and `creds.csv` to a private scratch dir — never put a real
|
|
68
|
+
tenant's config or a password into a shared or public place. Hand the credentials over
|
|
69
|
+
in the three-line format of
|
|
70
|
+
[Add a tenant admin](../users/add-tenant-admin.md#hand-the-credentials-over--always-this-format).
|
|
71
|
+
Staff of the school get their own accounts the same way (their address, their name);
|
|
72
|
+
the generic admin is not shared with them.
|
|
73
|
+
|
|
74
|
+
**Created for you, not by you:** the apply also provisions the tenant's test applicant
|
|
75
|
+
`test@example.com` („Test Bewerber"), which then shows up in `users list` with the new
|
|
76
|
+
slug. That is intended — see [Use a tenant's test account](../users/use-test-account.md).
|
|
77
|
+
|
|
78
|
+
The scaffold emits the smallest config `apply` accepts: the tenant block (all
|
|
79
|
+
grantable module permissions + `defaultApplicantGroupKey`), one school operator
|
|
80
|
+
(legal entity), **one pedagogy unit** (the school offers link to), the `tenant_admin`
|
|
81
|
+
+ `applicant` permission groups, and one display group (required — the system
|
|
82
|
+
identity fields attach to it). No users (provision them in step 5) and no `source`.
|
|
83
|
+
It is built by the same code that builds the platform's demo tenant, so the
|
|
84
|
+
scaffolded permission sets can never drift from it.
|
|
85
|
+
|
|
86
|
+
## Verify
|
|
87
|
+
```bash
|
|
88
|
+
# The tenant + its foundation now exist:
|
|
89
|
+
plexus-cli --env <env> --tenant <slug> config targets # foundation rows > 0
|
|
90
|
+
plexus-cli --env <env> --tenant <slug> config export # round-trips the unit, groups
|
|
91
|
+
plexus-cli --env <env> tenants # <slug> is listed
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then, in this order: the decisions above (terms, mail level, branding, payments) →
|
|
95
|
+
offers on the pedagogy unit: `offer init --unit <unit key> --title … --title-en … --responsible <staff configKey>` →
|
|
96
|
+
`offer apply --publish` (see `plexus-cli offer --help` and
|
|
97
|
+
[Publish an offer bundle](../offer/publish-offer-bundle.md)).
|
|
98
|
+
|
|
99
|
+
## Idempotency / gotchas
|
|
100
|
+
- **`config init` is offline and overwrites `--out`** — re-running regenerates the
|
|
101
|
+
same skeleton from `--tenant`/`--name`; it does not touch any deployment.
|
|
102
|
+
- **`config apply` is idempotent** — re-running reconciles to the config (no
|
|
103
|
+
duplicate tenant). Dry-run first; only `--confirm` writes.
|
|
104
|
+
- **`source` is optional.** Omit it for a non-importing tenant; add it only when
|
|
105
|
+
wiring a master-data import. Import code fails loud at import time if a source is
|
|
106
|
+
needed but absent — there is no fake-source fallback.
|
|
107
|
+
- **Teardown:** only `config wipe --target all --confirm --allow-destructive` deletes
|
|
108
|
+
the tenant **identity row** — after purging its applications and draining every
|
|
109
|
+
target, files included (only the logo stays: sent mail links it). `--target foundation` (even with `--cascade`) clears
|
|
110
|
+
child tables + dependent data but leaves the tenant row and sibling config targets —
|
|
111
|
+
by design (`tenants` is excluded from every wipe target). The CLI prints a note when
|
|
112
|
+
you wipe a non-`all` target so this is never a surprise.
|
|
113
|
+
- Staff accounts are a **separate plane** (Better Auth) — the config never creates
|
|
114
|
+
logins; provision them with `users provision` (step 5).
|
|
115
|
+
|
|
116
|
+
## Related
|
|
117
|
+
- [Onboard / establish a tenant](./onboard-tenant.md) — establish from an existing
|
|
118
|
+
`.dev` export / env (the non-scaffold path).
|
|
119
|
+
- [Update a tenant's config](./update-tenant-config.md) · [Wipe a tenant's config](./wipe-tenant-config.md)
|
|
120
|
+
- [Add a tenant admin](../users/add-tenant-admin.md) — the credentialed login (step 5).
|
|
121
|
+
- Command reference: `plexus-cli config <verb> --help`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Onboard / establish a tenant
|
|
2
|
+
|
|
3
|
+
**When to use:** stand up a real tenant's config-as-code in an environment for the first time
|
|
4
|
+
(or refresh it). A real tenant's config lives in the database (DB-authoritative) and is
|
|
5
|
+
established from a snapshot or another environment — never from built-in seed data.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `plexus-cli` installed; platform-admin token for the target env (`_platform.config.manage`).
|
|
10
|
+
- A config **source** for the tenant, one of:
|
|
11
|
+
- a `config export` from another env (default path `config/.dev/<slug>/config.json`),
|
|
12
|
+
- an environment that already has the tenant (promote with `--from env:<srcEnv>`).
|
|
13
|
+
A real tenant's config is kept nowhere but its deployment — prod is its source of truth, so a
|
|
14
|
+
refresh of staging comes FROM prod, never the other way round.
|
|
15
|
+
- The `--tenant` value must be the **slug embedded in the snapshot** — apply rejects a mismatch.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Establish from a snapshot (dry-run first, then commit).
|
|
21
|
+
plexus-cli config apply --env <env> --tenant <slug> \
|
|
22
|
+
--from snapshot:config/.dev/<slug>/config.json
|
|
23
|
+
plexus-cli config apply --env <env> --tenant <slug> \
|
|
24
|
+
--from snapshot:config/.dev/<slug>/config.json --confirm
|
|
25
|
+
|
|
26
|
+
# OR copy a tenant that already exists on prod to staging (re-plans against the target's live state).
|
|
27
|
+
plexus-cli config apply --env staging --tenant <slug> --from env:prod --confirm
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**If the source carries `foundation.users`, the committing apply hands you passwords.**
|
|
31
|
+
Every declared user without a login account gets one minted right here, printed once
|
|
32
|
+
(`+ account <email> password: …`) — the server keeps only the hash. Redirect them instead
|
|
33
|
+
of running this in a shared terminal:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
plexus-cli config apply --env <env> --tenant <slug> \
|
|
37
|
+
--from snapshot:<path> --confirm --output creds.csv --quiet
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The dry-run above mints nothing (`--output` is inert and creates no file); it lists the
|
|
41
|
+
same users as `! no account <email>`, so you know beforehand whether the commit will emit
|
|
42
|
+
secrets.
|
|
43
|
+
|
|
44
|
+
After the config exists, iterate it live with [Update a tenant's config](./update-tenant-config.md),
|
|
45
|
+
run the real import, and publish offers via `plexus-cli offer apply`.
|
|
46
|
+
|
|
47
|
+
## Verify
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
plexus-cli config targets --env <env> --tenant <slug> # registry + live per-tenant row counts
|
|
51
|
+
plexus-cli config export --env <env> --tenant <slug> # full reconstructed config
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Idempotency / gotchas
|
|
55
|
+
|
|
56
|
+
- `apply` is **idempotent and additive on config tables** — it never wipes the data plane
|
|
57
|
+
(parties/values/applications). Re-running an establish is safe.
|
|
58
|
+
- **Deploys do not establish real tenants.** A staging/prod deploy applies only the
|
|
59
|
+
**template tenant** (`test`) — establishing a real tenant is this manual step.
|
|
60
|
+
- The **import mapping is DB-authoritative**: `custom_fields.sourceColumns` ships in the config
|
|
61
|
+
and is read by the import worker. The import **fails loud** if a tenant has fields but none
|
|
62
|
+
carry `sourceColumns` — i.e. the worker was deployed before the tenant was (re)applied:
|
|
63
|
+
re-run this apply.
|
|
64
|
+
- A structural schema change is a **migration** that a platform operator runs before the
|
|
65
|
+
apply — not part of onboarding, and not something the CLI does.
|
|
66
|
+
|
|
67
|
+
## Related
|
|
68
|
+
|
|
69
|
+
- [Update a tenant's config](./update-tenant-config.md) · [Wipe a tenant's config](./wipe-tenant-config.md)
|
|
70
|
+
- [Scaffold a brand-new tenant config](./init-tenant.md) — when no source exists yet.
|
|
71
|
+
- Command semantics: `plexus-cli config apply --help`.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Rename, add, reorder or retire a program stage (Klassenstufe)
|
|
2
|
+
|
|
3
|
+
**Use it to** change an offer's Klassenstufen — rename "Kindergarten Jahr 1" to
|
|
4
|
+
"Nursery 1", add a level, change the order, or retire one — without leaving a
|
|
5
|
+
second, stale level behind and without cutting applications loose.
|
|
6
|
+
|
|
7
|
+
## The model in one paragraph
|
|
8
|
+
|
|
9
|
+
A stage is identified by the **key** in `bundle.yaml → program.stages[].key`, never
|
|
10
|
+
by its name. The stored key is `<program.key>-stufe-<key>`. Applications, capacity
|
|
11
|
+
cells and waitlists point at the stage row by id, so a rename that keeps the key
|
|
12
|
+
keeps all of them. `offer apply` reconciles by key: same key → update, new key →
|
|
13
|
+
insert, removed key → archive (refused while an active application sits on it),
|
|
14
|
+
re-listed archived key → restore. Array order = display order.
|
|
15
|
+
|
|
16
|
+
## Declaratively (bundle — preferred for anything you want reproducible)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Start from what is live — prod is the source of truth for a real tenant, so
|
|
20
|
+
# work on a fresh export in a scratch directory (export writes the bare keys back)
|
|
21
|
+
WORK=$(mktemp -d)
|
|
22
|
+
plexus-cli --env prod --tenant <slug> offer export --slug <offer-slug> --out $WORK/<offer-slug>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
# 2. Edit program.stages — KEEP the key of every level you rename. A name is
|
|
27
|
+
# German AND English; changing only the English is a rename too.
|
|
28
|
+
program:
|
|
29
|
+
stages:
|
|
30
|
+
- key: kindergarten-jahr-1 # existing level, new name
|
|
31
|
+
name: { de: Nursery 1, en: Nursery 1 }
|
|
32
|
+
- key: kindergarten-jahr-2
|
|
33
|
+
name: { de: Nursery 2, en: Nursery 2 }
|
|
34
|
+
- key: early-years-1 # new level
|
|
35
|
+
name: { de: Early Years 1, en: Early Years 1 }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 3. Offline check, then look at the stage rows of the plan
|
|
40
|
+
plexus-cli offer validate --dir $WORK/<offer-slug>
|
|
41
|
+
plexus-cli --env prod offer plan --dir $WORK/<offer-slug>
|
|
42
|
+
# ~ stage:kindergarten-jahr-1 ~stage kindergarten-jahr-1 ("Kindergarten Jahr 1" → "Nursery 1", en "Kindergarten year 1" → "Nursery 1")
|
|
43
|
+
# + stage:early-years-1 +stage early-years-1 (Early Years 1)
|
|
44
|
+
|
|
45
|
+
# 4. Apply
|
|
46
|
+
plexus-cli --env prod offer apply --allow-prod --dir $WORK/<offer-slug>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Imperatively (one-off, no bundle)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# --level takes the level's KEY; a German rename always needs --name-en (the
|
|
53
|
+
# English is never copied from the German, and a name without English is refused).
|
|
54
|
+
# Against prod: --env prod … --allow-prod
|
|
55
|
+
plexus-cli --env <env> offer level rename --slug <offer-slug> --level kindergarten-jahr-1 --name "Nursery 1" --name-en "Nursery 1"
|
|
56
|
+
plexus-cli --env <env> offer level add --slug <offer-slug> --name "Early Years 1" --name-en "Early Years 1" --key early-years-1
|
|
57
|
+
plexus-cli --env <env> offer level remove --slug <offer-slug> --level early-years-1
|
|
58
|
+
plexus-cli --env <env> offer level restore --slug <offer-slug> --level early-years-1
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
After imperative changes, **export the bundle again before the next `apply`**:
|
|
62
|
+
otherwise the old bundle still lists the old state, and applying it undoes your
|
|
63
|
+
changes (renames them back, archives the added levels).
|
|
64
|
+
|
|
65
|
+
## Verify
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
plexus-cli --env prod offer plan --dir $WORK/<offer-slug> # no stage rows = in sync
|
|
69
|
+
plexus-cli --env prod offer show --slug <offer-slug> # levels in order
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Gotchas
|
|
73
|
+
|
|
74
|
+
- **Changing a key is not a rename.** A new key inserts a new level and archives the old
|
|
75
|
+
one. If the old one has active applications, the apply is refused — which is what you
|
|
76
|
+
want: those applications would stay on a level nobody sees.
|
|
77
|
+
- **`[stage/hasActiveApplications]`** — the level you dropped (or `level remove`d) still
|
|
78
|
+
has active applications. `offer plan` shows it in advance: `-stage … — REFUSED: n
|
|
79
|
+
active application(s)`. Rename it instead, or close those applications first. No
|
|
80
|
+
stage was changed: the reconcile is one transaction. (The manifest draft and the
|
|
81
|
+
unit/program link apply wrote before it stay — unpublished; don't publish by hand.)
|
|
82
|
+
- **A level moves to another offer** (e.g. Grade 12 moves from Primary to Secondary). Keys are per
|
|
83
|
+
program, so the target offer gets a *new* row and the source offer's row is archived.
|
|
84
|
+
Nothing re-points an application across programs. While applications are active on
|
|
85
|
+
the source row, keep the level listed in the source bundle and close its cell
|
|
86
|
+
(`offer capacity set --slug … --run … --level … --close`), then drop it in a later apply.
|
|
87
|
+
- **`[stage/hasOpenLeads]`** is a warning, not a refusal: open Interessenten keep
|
|
88
|
+
pointing at the archived level and cannot be converted until they are moved to an
|
|
89
|
+
active level.
|
|
90
|
+
- **`[stage/duplicateKey]` from apply but not from validate** — the stored key is taken
|
|
91
|
+
by a stage of *another* program in the tenant (keys are unique per tenant). Choose a
|
|
92
|
+
different key.
|
|
93
|
+
- **A `-stage` row showing a key like `kindergarten:vorschule:lz3k9`** — a legacy stage
|
|
94
|
+
created in the master-data admin before stage keys existed, keyed outside the `<program>-stufe-`
|
|
95
|
+
scheme. No bundle key can address it, so `apply` archives it (through the guard).
|
|
96
|
+
`offer export` refuses such a row with `[stage/foreignKey]`. No CLI verb re-keys a
|
|
97
|
+
stage today — raise it with the platform operator instead of hand-editing around it.
|
|
98
|
+
- A bundle **without** `program.stages` leaves the levels alone; `stages: []` archives
|
|
99
|
+
all of them (guarded).
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Set a tenant's external-portal branding
|
|
2
|
+
|
|
3
|
+
**When to use:** give a tenant's public `/e/**` Bewerbungsportal its own white-label
|
|
4
|
+
look — brand colors, corner radius, logo size, header style, and font families, plus an
|
|
5
|
+
uploaded logo and uploaded font files — on staging and/or prod, without a reseed.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `plexus-cli` installed and current (`plexus-cli --help`).
|
|
10
|
+
- A token configured for each target env (`plexus-cli --env <env> config show` prints
|
|
11
|
+
the resolved connection): a platform-admin token holding `_platform.config.manage` (the
|
|
12
|
+
same right that gates `config apply`), or — for your OWN tenant only — a tenant
|
|
13
|
+
admin's token holding `administration.config.manage` (see
|
|
14
|
+
[CLI access for a tenant admin](../users/cli-access-for-tenant-admin.md)). Such a
|
|
15
|
+
token changes branding **only through `config apply`** (branding is one of the few
|
|
16
|
+
`tenant.*` settings it may change); every `plexus-cli branding …` command needs the
|
|
17
|
+
platform right and refuses it. A local admin key without a user identity does **not**
|
|
18
|
+
satisfy this gate.
|
|
19
|
+
- The `plexus-cli branding …` commands must be **live on the target env** first (they
|
|
20
|
+
call backend functions of the same release). A platform operator rolls a release out
|
|
21
|
+
to staging, then prod; only then run the CLI.
|
|
22
|
+
- The tenant **slug** (e.g. `test`). Always **stage on staging first**, verify in the
|
|
23
|
+
browser, then promote to prod.
|
|
24
|
+
|
|
25
|
+
## The two halves of branding
|
|
26
|
+
|
|
27
|
+
| Part | Where it lives | How to set it |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| **Theme tokens** — colors, radius, logoSize, bannerDensity, filledHeader, websiteUrl, font **families** | `tenant_branding` row columns; config-portable (exported in `tenant.branding`) | `plexus-cli branding set-theme` (surgical) **or** `config apply` (whole-config) |
|
|
30
|
+
| **Logo** | `_storage` id column (a single `primary` slot); carried by config **by hash** (`tenant.brandingAssets.logo`) | `plexus-cli branding set-logo` (one-off upload) **or** `config apply` (bytes from the snapshot / source env) |
|
|
31
|
+
| **Font files** (WOFF2, one per slot `heading` / `body`) | `_storage` id columns; carried by config **by hash** (`tenant.brandingAssets.fontHeading` / `fontBody`) | `plexus-cli branding set-font --slot heading\|body --file <woff2>` **or** `config apply` |
|
|
32
|
+
|
|
33
|
+
A `storageId` is deployment-local, so config never carries it. It carries each uploaded
|
|
34
|
+
file like a library file: `{ sha256, sizeBytes, mimeType }` under `tenant.brandingAssets`, and
|
|
35
|
+
`config export` downloads the bytes beside `config.json` as `branding/<slot>.<ext>`
|
|
36
|
+
(`path` in the entry). `config apply --from snapshot:…` or `--from env:<src>` uploads
|
|
37
|
+
only the files whose hash the target does not hold; the dry run prints
|
|
38
|
+
`~ brand branding/logo content differs — would upload`. A missing slot — or a snapshot
|
|
39
|
+
without the block — leaves the target's file untouched. A replaced **logo** file is never
|
|
40
|
+
deleted (sent mail links it); a replaced **font** file is.
|
|
41
|
+
Fonts are served from the app's own Convex origin — **never** point a browser at an
|
|
42
|
+
external font host (a private-network host is blocked by the browser; the Google-Fonts
|
|
43
|
+
CDN is a GDPR problem).
|
|
44
|
+
|
|
45
|
+
## The theme shape
|
|
46
|
+
|
|
47
|
+
Every field is optional — omit a key to fall back to the platform default:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
{
|
|
51
|
+
"colors": {
|
|
52
|
+
"primary": "#1E4BCD", // brand primary → --color-primary (buttons, rings, filled header)
|
|
53
|
+
"accent": "#0AAA5F", // secondary accent → --color-accent (links, hovers, dots)
|
|
54
|
+
"background": "#FFFFFF", // portal page bg → --color-bg
|
|
55
|
+
"foreground": "#111111" // body text → --color-fg
|
|
56
|
+
},
|
|
57
|
+
"radius": "sharp", // "sharp" | "rounded" | "soft"
|
|
58
|
+
"logoSize": "max", // banner logo height: "sm" | "md" | "lg" | "max" (default lg)
|
|
59
|
+
"bannerDensity": "normal", // "compact" | "normal" | "spacious" (default normal)
|
|
60
|
+
"filledHeader": false, // true → top nav bar filled with the PRIMARY colour (on-primary text)
|
|
61
|
+
"websiteUrl": "https://www.example-schule.de",
|
|
62
|
+
"fonts": {
|
|
63
|
+
"heading": { "family": "Space Grotesk" }, // family only — the file is uploaded (set-font)
|
|
64
|
+
"body": { "family": "Lora" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `colors.*` are `#RRGGBB` hex strings; `radius`/`logoSize`/`bannerDensity` are the enums above.
|
|
70
|
+
- A font entry is `{ family }`. With an uploaded file for that slot the portal loads it as
|
|
71
|
+
`@font-face` under that family; without one the family must already be available
|
|
72
|
+
(system/bundled). A font `url` is **refused** (`fonts.<slot>.url is no longer
|
|
73
|
+
supported`) — upload the file instead.
|
|
74
|
+
- Bad values (malformed hex, bad enum, a font `url`) fail loud server-side — the write
|
|
75
|
+
aborts, no partial state.
|
|
76
|
+
|
|
77
|
+
## Steps — the surgical CLI path (recommended)
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 1. Inspect current branding (branding + resolved logo URL).
|
|
81
|
+
plexus-cli --env staging --tenant <slug> branding show
|
|
82
|
+
|
|
83
|
+
# 2. Set the theme tokens from a JSON file (replaces the theme wholesale; leaves the logo).
|
|
84
|
+
plexus-cli --env staging --tenant <slug> branding set-theme --file theme.json
|
|
85
|
+
|
|
86
|
+
# 3. Upload the logo — from a local file …
|
|
87
|
+
plexus-cli --env staging --tenant <slug> branding set-logo --file logo.png
|
|
88
|
+
# … or straight from a URL (fetched + uploaded by the CLI):
|
|
89
|
+
plexus-cli --env staging --tenant <slug> branding set-logo \
|
|
90
|
+
--url https://cdn.example.com/logo-black.png
|
|
91
|
+
|
|
92
|
+
# 4. Upload the font files — the slot's family must be set first (step 2), the file is
|
|
93
|
+
# applied under it. WOFF2 only (signature-checked), ≤ 2 MB; a replaced file is deleted.
|
|
94
|
+
plexus-cli --env staging --tenant <slug> branding set-font --slot heading --file heading.woff2
|
|
95
|
+
plexus-cli --env staging --tenant <slug> branding set-font --slot body --file body.woff2
|
|
96
|
+
# (`branding clear-font --slot <slot>` removes a file; the family stays.)
|
|
97
|
+
|
|
98
|
+
# 5. Verify, then promote to prod (re-run 1–4 with --env prod — never assume prod == staging).
|
|
99
|
+
plexus-cli --env prod --tenant <slug> branding show
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- `set-theme` replaces the whole theme (a submitted-absent field clears the stored one),
|
|
103
|
+
mirroring the admin form — so the file must carry the **complete**
|
|
104
|
+
theme you want.
|
|
105
|
+
- The logo cap: images only, ≤ 3 MB. Pick the variant that reads on the header — a **dark
|
|
106
|
+
logo** for the default light bar, a **white/inverse logo** when `filledHeader: true`
|
|
107
|
+
(the bar is the dark primary colour). `branding clear-logo` removes it.
|
|
108
|
+
|
|
109
|
+
## Alternatives
|
|
110
|
+
|
|
111
|
+
- **Whole-config rollout (`config apply`).** The theme round-trips through the config
|
|
112
|
+
snapshot's `tenant.branding` block and the files through `tenant.brandingAssets`, so
|
|
113
|
+
`config export → edit → apply` (or `apply --from env:staging` onto prod) carries the
|
|
114
|
+
complete brand, bytes included — but it re-plans the *entire* tenant config, so prefer
|
|
115
|
+
it when branding rides along with other config changes or moves between environments.
|
|
116
|
+
To swap a file in a snapshot, replace `branding/<slot>.<ext>` and keep its `path`: the
|
|
117
|
+
CLI re-hashes the file on disk. For a one-off change, `set-theme` / `set-logo` /
|
|
118
|
+
`set-font` are surgical.
|
|
119
|
+
- **Admin UI.** A tenant admin (holding `administration.branding.manage`) edits the same
|
|
120
|
+
row live under **Administration → Branding**. Use it for quick per-tenant tweaks; use the CLI
|
|
121
|
+
for reproducible, cross-env rollout by a platform admin.
|
|
122
|
+
|
|
123
|
+
## Gotchas
|
|
124
|
+
|
|
125
|
+
- **Release gate:** a brand-new `plexus-cli branding` verb (or a new theme field) must be
|
|
126
|
+
live on the env before the CLI call resolves — otherwise the call fails with an unknown
|
|
127
|
+
function. Ask the platform operator whether the release is out.
|
|
128
|
+
- `config export` names the logo and the font files by hash and writes their bytes to
|
|
129
|
+
`branding/`; `branding show` resolves their URLs (`logoUploads`, `fontUploads`). A
|
|
130
|
+
committing apply refuses a changed hash it has no bytes for
|
|
131
|
+
(`[branding/contentMissing]`), a font file that is not WOFF2 (`[branding/fontRefused]`)
|
|
132
|
+
and a font slot without an own family (`[branding/fontFamilyMissing]`).
|
|
133
|
+
- `set-font` refuses (`INVALID`, German message) a slot without a saved
|
|
134
|
+
family, a family named like the back office's own fonts (`Inter Variable`, `Azeret Mono
|
|
135
|
+
Variable` — an `@font-face` is document-wide), anything over 2 MB and anything that is
|
|
136
|
+
not WOFF2 — convert TTF/OTF/WOFF first (e.g. `woff2_compress`).
|
|
137
|
+
- Removing a family (config / `set-theme`) leaves its file attached but unused; the admin
|
|
138
|
+
page flags it. Clear the file with `branding clear-font` when you drop the family.
|
|
139
|
+
- A deployment that once held external font URLs dropped them on upgrade (the platform
|
|
140
|
+
operator has the list of dropped URLs); re-upload those files with `set-font`.
|
|
141
|
+
- Verify in the browser: DevTools → Network → the `.woff2` request from the Convex origin
|
|
142
|
+
answers 200 with `Access-Control-Allow-Origin: <the portal origin>` (fonts load
|
|
143
|
+
cross-origin; Convex storage reflects the requesting origin).
|
|
144
|
+
- This is a live-DB operation; nothing else records it. A `config export` snapshot is
|
|
145
|
+
your only copy — keep it out of any shared or public place.
|
|
146
|
+
|
|
147
|
+
## Related
|
|
148
|
+
|
|
149
|
+
- [Update a tenant's config](./update-tenant-config.md) · [Onboard / establish a tenant](./onboard-tenant.md)
|
|
150
|
+
- Command semantics: `plexus-cli branding <verb> --help`, `plexus-cli config apply --help`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Set a school's terms („Anmeldung" statt „Bewerbung")
|
|
2
|
+
|
|
3
|
+
**When to use:** a school calls the process by another name — it runs an
|
|
4
|
+
„Anmeldeverfahren", not a „Bewerbungsverfahren". One setting switches the whole product for that school: the UI,
|
|
5
|
+
task titles, feed and party notes, applicant-name fallbacks, mail blocks and buttons,
|
|
6
|
+
and every refusal the browser shows. URLs (`/e/units/<unit>/bewerben`) stay as they are.
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
- `tenant.terms` in the tenant config — today one term: `application`, a **closed
|
|
11
|
+
choice**: `"Bewerbung"` (platform default) or `"Anmeldung"`. The browser and the
|
|
12
|
+
backend read the same rules, so both always say the same word.
|
|
13
|
+
- Stored on `tenants.terms` **only when it deviates**. Absent = the platform's word.
|
|
14
|
+
- An unknown term or choice is refused by `config set` / `config apply`
|
|
15
|
+
(`[terms/unknownTerm]`, `[terms/unknownChoice]`) — nothing silently falls back.
|
|
16
|
+
- The person follows the choice: „Bewerber:in" → „Anmelder:in", „Bewerberportal" →
|
|
17
|
+
„Anmeldeportal".
|
|
18
|
+
- Not rewritten: a school's own texts (letter deviations, offer/board labels, team
|
|
19
|
+
names — change those via `offer`/`config` as usual).
|
|
20
|
+
|
|
21
|
+
## Set it (one command, dry-run first)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# dry run — prints the one-row plan
|
|
25
|
+
plexus-cli --env staging config set --tenant <slug> --target tenant \
|
|
26
|
+
--key <slug> --field terms --value '{"application":"Anmeldung"}'
|
|
27
|
+
# commit
|
|
28
|
+
plexus-cli --env staging config set --tenant <slug> --target tenant \
|
|
29
|
+
--key <slug> --field terms --value '{"application":"Anmeldung"}' --confirm
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Equivalent through a scratch export (a real tenant's config lives only in its
|
|
33
|
+
deployment — keep the export private): `config export --tenant <slug> --out <scratch>/config.json` → set
|
|
34
|
+
`"tenant": { …, "terms": { "application": "Anmeldung" } }` → `config apply --tenant
|
|
35
|
+
<slug> --from snapshot:<scratch>/config.json` (dry run) → `--confirm`.
|
|
36
|
+
|
|
37
|
+
Back to the platform word: `--value '{}'` (or remove the block and apply).
|
|
38
|
+
|
|
39
|
+
## Verify
|
|
40
|
+
|
|
41
|
+
- `plexus-cli --env <env> config export --tenant <slug>` shows `tenant.terms`.
|
|
42
|
+
- Portal `https://<host>/<slug>/e` shows „Jetzt anmelden", nav „Anmelden", the
|
|
43
|
+
sign-in button „Einloggen"; the first server-rendered frame already says it (no flash).
|
|
44
|
+
- Staff board: „Anmeldungen", „Anmeldung ablehnen", …
|
|
45
|
+
|
|
46
|
+
## Gotchas
|
|
47
|
+
|
|
48
|
+
- Texts the server **stored** before the switch (feed notes, party notes, task titles
|
|
49
|
+
already spawned) keep the word they were written with — history is not rewritten.
|
|
50
|
+
- The portal's English UI is unaffected (terms are German words).
|