157 lines
7.4 KiB
Markdown
157 lines
7.4 KiB
Markdown
# Placeholder System Guidelines
|
|
|
|
This note is the working guideline for Lumi placeholder and variable support.
|
|
Future placeholder work should follow this model unless the architecture is
|
|
explicitly revised.
|
|
|
|
## Architecture
|
|
|
|
Placeholders are registered server-side through `src/services/placeholders.js`.
|
|
The frontend must not declare placeholder permissions, allowed plugins, or
|
|
sensitivity. Editable fields reference a trusted `field_id`, and the server
|
|
uses that field policy to decide which placeholders are available.
|
|
|
|
Core and plugins can register:
|
|
|
|
- placeholder definitions with metadata and a resolver function
|
|
- field policies for template/input destinations that support placeholders
|
|
|
|
Catalog, preview, save validation, and runtime rendering must share the same
|
|
permission-checking path. Saved templates are not trusted permanently; they must
|
|
be revalidated at render time because plugin availability, policies, settings,
|
|
or sensitivity can change.
|
|
|
|
## Naming
|
|
|
|
Use namespaced double-brace tokens:
|
|
|
|
- `{{core.main.bot_name}}`
|
|
- `{{core.main.command_prefix}}`
|
|
- `{{core.command.rng}}`
|
|
- `{{custom.stream.title}}`
|
|
- `{{user.public.display_name}}`
|
|
- `{{platform.discord.guild.member_count}}`
|
|
- `{{okf.file.community.currency.primary_name}}`
|
|
- `{{plugin.throne_wishlist.item_name}}`
|
|
|
|
Avoid unqualified names for new work. Compatibility aliases may exist for old
|
|
templates, but the catalog should present canonical namespaced tokens.
|
|
|
|
Admin-managed values live under `{{custom.*}}`. Admins create them under
|
|
Settings > Reusable text values; names are validated, cannot collide with
|
|
system/plugin namespaces, and are stored in the update-preserved settings
|
|
database.
|
|
|
|
## Core Namespaces
|
|
|
|
Current core-owned namespaces include:
|
|
|
|
- `core.main.*`: safe Lumi core settings such as bot/site name and command prefix
|
|
- `core.command.*`: values created for one command execution, such as a Random Reply RNG roll
|
|
- `custom.*`: admin-managed reusable values with explicit user, moderator, or admin visibility
|
|
- `user.public.*`: safe viewer/triggering-user display information
|
|
- `platform.discord.guild.*`: safe Discord guild statistics from the configured guild
|
|
- `platform.twitch.channel.*`: safe locally configured Twitch channel/runtime values
|
|
- `platform.youtube.channel.*`: safe locally configured or hydrated YouTube channel/runtime values
|
|
- `okf.file.*`: file-backed OKF frontmatter values, with legacy aliases such as `{{community.currency.primary_name}}`
|
|
|
|
Each platform namespace exposes a public-safe `data_status` value. Current values are `live_cache`, `connected`, `configured_not_connected`, or `unavailable`, depending on the integration. Discord statistics read only the connected client's guild cache; placeholder rendering never triggers a Discord fetch.
|
|
|
|
Twitch follower/subscriber counts and Twitch/YouTube scheduled-start values remain intentionally unavailable. The current Twitch integration is chat-only, and the YouTube runtime does not maintain those statistics. Add them only after a background refresh service stores value, fetched-at time, expiry/stale state, and last error. Rendering must read that cache without a provider API call and return a safe unavailable/stale result when credentials or rate limits prevent refresh.
|
|
|
|
## Sensitivity
|
|
|
|
Supported sensitivity levels:
|
|
|
|
- `public_safe`: safe in user-visible output
|
|
- `user`: user-specific content, only for fields whose output audience allows it
|
|
- `moderator`: moderator/support visible content
|
|
- `admin`: admin-only content
|
|
- `internal`: internal implementation/runtime content
|
|
- `secret_never_render`: must not be registered, listed, previewed, or rendered
|
|
|
|
Never expose API keys, tokens, cookies, passwords, database URLs, session IDs,
|
|
raw secrets, private file paths, sensitive query strings, or unredacted raw
|
|
diagnostics through placeholders. If diagnostics are needed, register explicit
|
|
redacted safe variants.
|
|
|
|
## Field Policies
|
|
|
|
Every placeholder-compatible input must use a stable `field_id` whose policy is
|
|
registered by trusted backend code. A field policy should define:
|
|
|
|
- `field_id`
|
|
- label and field type
|
|
- output audience
|
|
- minimum editor role
|
|
- allowed namespaces or placeholder IDs
|
|
- maximum sensitivity
|
|
|
|
Security is based on the intersection of:
|
|
|
|
- current editor role/capability during catalog, preview, and save
|
|
- field policy
|
|
- output audience that will see rendered content
|
|
- placeholder sensitivity
|
|
- placeholder minimum viewer role
|
|
- plugin availability
|
|
- runtime context
|
|
|
|
Current trusted field policies:
|
|
|
|
| Field ID | Destination | Editor | Output | Allowed values |
|
|
| --- | --- | --- | --- | --- |
|
|
| `core.custom_commands.static_response` | Static and Random Reply custom-command replies | Moderator | User | Public-safe `core.main.*`, `core.command.*`, `custom.*`, and `user.public.*` |
|
|
| `plugin.throne_wishlist.message_template` | Throne event announcements | Admin | User | Public-safe Throne event placeholders |
|
|
| `okf.markdown` | SQLite/community OKF Markdown | OKF contributor | Selected role | Permission-filtered core, platform, and visible OKF values |
|
|
|
|
The current WebUI contains no inline JSON placeholder catalogs; double-brace editors use these server-owned IDs. Welcome Messages and Auto VC retain their older `{username}` and `[username]` runtime token formats for compatibility. They are not treated as generic placeholders until their event-context renderers and saved templates are migrated together; adding only autocomplete would advertise tokens those runtimes cannot safely resolve.
|
|
|
|
An admin editing a user-visible template is not enough to allow admin-only
|
|
placeholders. The output audience still limits what can render.
|
|
|
|
Custom values use the same catalog, validation, preview, and runtime checks as
|
|
code-registered placeholders. Values marked for moderators or admins do not
|
|
appear in lower-privilege catalogs and cannot render into user-visible command
|
|
replies. The `custom` namespace is reserved for this registry; plugins must use
|
|
their own `plugin.*` namespace.
|
|
|
|
## Plugin Registration
|
|
|
|
Plugins should register placeholders during backend plugin initialization using
|
|
the placeholder service passed to `init` or available through
|
|
`web.placeholders`. Plugin definitions should include `plugin_id` so disabled or
|
|
missing plugins can fail safely.
|
|
|
|
Resolvers must return safe display values and avoid leaking raw internal data.
|
|
Renderers that send chat messages or HTML should still sanitize or escape for
|
|
their output context after placeholder resolution.
|
|
|
|
## Frontend
|
|
|
|
Frontend fields use:
|
|
|
|
```html
|
|
data-placeholder-field="plugin.example.message_template"
|
|
data-placeholder-output-audience="user"
|
|
```
|
|
|
|
The frontend requests `/api/placeholders/catalog?field_id=...` and uses the
|
|
returned catalog for autocomplete and tree browsing. Inline JSON placeholder
|
|
lists are a transitional fallback only and should not be used for new fields.
|
|
|
|
## Validation
|
|
|
|
Templates must be checked:
|
|
|
|
- when the editor opens a catalog
|
|
- during preview
|
|
- before save
|
|
- at runtime before rendering output
|
|
|
|
Unauthorized placeholders should fail closed. User-visible output should show a
|
|
generic unavailable marker or leave legacy unknown tokens unchanged, depending
|
|
on the existing renderer contract, but it must not reveal the restricted value.
|
|
|
|
Async resolvers are bounded by a short timeout (1.5 seconds by default). Duplicate and adjacent tokens are resolved by source position, and resolver output is inserted literally rather than scanned again. A failed, unavailable, forbidden, or timed-out value therefore affects only that token and renders the caller's safe fallback.
|