mirror of
https://github.com/discourse/discourse.git
synced 2026-08-05 16:23:34 +08:00
Adding new site settings tends to involve the same small convention checks around naming, descriptions, client exposure, admin grouping, and whether the setting reads clearly in the UI. This PR adds a generic `discourse-site-settings` skill for authoring and reviewing Discourse site settings. It includes guidance for core and plugin setting files, i18n descriptions, validators for dependent settings, browser verification of rendered setting copy, and review checks. It also documents a proposed category-list pattern using paired `xyz_category_scope` and `xyz_categories` settings, including the include, exclude, strict, public, and all modes.
225 lines
10 KiB
Markdown
Vendored
225 lines
10 KiB
Markdown
Vendored
---
|
|
name: discourse-site-settings
|
|
description: Use when adding, modifying, renaming, or reviewing Discourse site settings in core or plugins. Covers config/site_settings.yml and plugin config/settings.yml, setting names, types, defaults, client exposure, areas, validators, i18n descriptions, category/category_list conventions, category scope dropdowns, access patterns, cache implications, tests, browser verification, and migration handoffs.
|
|
---
|
|
|
|
# Discourse Site Settings
|
|
|
|
Use this skill before writing or reviewing ordinary Discourse site settings. For upcoming-change
|
|
feature flags, also use `discourse-upcoming-changes`. For database migrations that rename or
|
|
remove settings, use `discourse-migration`.
|
|
|
|
## Workflow
|
|
|
|
1. Find nearby examples with `rg`; avoid reading all of `config/site_settings.yml`.
|
|
2. Put core settings in `config/site_settings.yml`; put plugin settings in
|
|
`plugins/<plugin>/config/settings.yml`.
|
|
3. Add an i18n description under `en.site_settings.<setting_name>` in the matching
|
|
`config/locales/server.en.yml`.
|
|
4. Set `client: true` only when frontend code must read the setting through `siteSettings`.
|
|
5. Prefer an `area:` for admin grouping when the setting belongs on a filtered settings page.
|
|
6. Access settings as `SiteSetting.setting_name` in Ruby and through `@service siteSettings` in JS.
|
|
7. Add a validator when a setting depends on another site setting for correctness.
|
|
8. Write focused tests for behavior controlled by the setting, including default behavior.
|
|
9. Use Playwright/browser to visit the admin site settings page and verify the setting name and
|
|
i18n description render correctly.
|
|
|
|
## Naming
|
|
|
|
- Use a name that describes the behavior, not only the implementation.
|
|
- Use the conventional suffix for typed settings:
|
|
- `*_groups` for group lists.
|
|
- `*_categories` for `type: category_list`.
|
|
- `*_category` for `type: category`.
|
|
- Avoid `_ids` in public site setting names for category lists even though values are stored as ids.
|
|
- For paired settings, prepend the same feature name so admins can recognize the settings as a
|
|
related group.
|
|
|
|
## Descriptions
|
|
|
|
- Write descriptions for admins, not developers.
|
|
- State the default or safest interpretation when ambiguity is likely.
|
|
- Mention privacy/security boundaries that always apply.
|
|
- If a setting only matters when another setting has a certain value, say so in the description.
|
|
- When referencing another site setting, use the `{{setting:setting_name}}` token instead of writing
|
|
the raw setting name or hardcoding the translated label. This lets the admin UI render the
|
|
referenced setting consistently and avoids copy drifting when labels change.
|
|
- Keep option names consistent with enum values and admin labels.
|
|
|
|
## Common Options
|
|
|
|
- `default`: choose a safe default for existing sites. For upload settings, use the id of the
|
|
seeded upload from `db/fixtures/010_uploads.rb`.
|
|
- `min`, `max`, `regex`, and `validator`: enforce value constraints server-side; descriptions and
|
|
frontend affordances are not enough.
|
|
- `mandatory_values`: pipe-separated values that must always remain in the setting. Common for
|
|
`group_list` settings that must always include admins/moderators.
|
|
- `disallowed_groups`: pipe-separated group ids hidden from group selectors and stripped from API
|
|
updates. This only applies to `group_list` settings.
|
|
- `requires_confirmation`: use for risky changes that need an admin confirmation dialog. Valid
|
|
values are `simple`, `simple_on_enable`, and `simple_on_disable`; add matching client i18n under
|
|
`admin.site_settings.requires_confirmation_messages.<setting_name>` when the default copy is not
|
|
specific enough.
|
|
- `themeable: true`: only for client-side UI settings that themes may override. Prefer simple types
|
|
such as `bool`, `integer`, `list`, or `enum`; normal `SiteSetting.setting = value` writes are not
|
|
allowed for themeable settings.
|
|
- `localizable`: use for public text/content settings that content localization may translate. Use
|
|
a hash for metadata such as `max_length:` and `cooked: true` when localized markdown should be
|
|
cooked.
|
|
- `area`: use the filtered admin settings page key when the setting belongs with an existing admin
|
|
workflow.
|
|
- `refresh: true`: use when clients should reload after the setting changes.
|
|
|
|
## Setting Types
|
|
|
|
Start with the simplest type that matches the admin's mental model:
|
|
|
|
- `bool`: on/off behavior.
|
|
- `integer` / `float`: numeric values; set `min`/`max` where possible.
|
|
- `enum`: one value from a fixed set; use `enum: "ClassName"` for reusable translated enums.
|
|
- `list`: multiple values from choices; use `list_type: compact` for selector-style UI and
|
|
`list_type: simple` for reorderable item lists.
|
|
- `group` / `group_list`: one or many groups; use `mandatory_values` and `disallowed_groups` where
|
|
relevant.
|
|
- `category` / `category_list`: one or many categories through the category picker.
|
|
- `tag_list`, `emoji_list`, `tag_group_list`, `host_list`, `email`, `username`, `color`, `icon`,
|
|
`upload`, `uploaded_image_list`, and `file_size_restriction`: use when the name describes the
|
|
data shape directly.
|
|
- `json_schema` / `objects`: structured settings. Prefer these only when a scalar/list setting is
|
|
not enough, and keep tests around schema validation and serialization.
|
|
|
|
### Category Lists
|
|
|
|
Use a plain `type: category_list` when the setting means exactly "these categories". Do not add a
|
|
scope enum just because the setting involves categories. For example, user-default settings such as
|
|
`default_categories_watching` should stay as a single category list; admins are selecting the
|
|
categories to apply, not defining an include/exclude query scope.
|
|
|
|
```yaml
|
|
default_categories_watching:
|
|
type: category_list
|
|
default: ""
|
|
area: "user_defaults"
|
|
```
|
|
|
|
Use the two-setting category-scope pattern only when the feature filters a topic/post result set and
|
|
admins may reasonably need all/public/include/exclude choices or subcategory control.
|
|
|
|
```yaml
|
|
xyz_category_scope:
|
|
type: enum
|
|
default: "public"
|
|
enum: "CategoryScopeSiteSetting"
|
|
client: false
|
|
area: "feature-name"
|
|
|
|
xyz_categories:
|
|
type: category_list
|
|
default: ""
|
|
client: false
|
|
area: "feature-name"
|
|
depends_on:
|
|
- xyz_category_scope
|
|
depends_on_values:
|
|
xyz_category_scope:
|
|
- include
|
|
- include_strict
|
|
- exclude
|
|
- exclude_strict
|
|
depends_behavior: "hidden"
|
|
dependent_setting_display: "inline"
|
|
```
|
|
|
|
Semantics:
|
|
|
|
- `all`: all regular topics.
|
|
- `public`: all non-read-restricted regular topics.
|
|
- `include`: use `xyz_categories` and their subcategories.
|
|
- `include_strict`: use only `xyz_categories`.
|
|
- `exclude`: start from all regular topics, then remove `xyz_categories` and their subcategories.
|
|
- `exclude_strict`: start from all regular topics, then remove only `xyz_categories`.
|
|
|
|
The `CategoryScopeSiteSetting` enum provides translated admin labels for these stored values. Pair
|
|
it with `depends_on_values`, `depends_behavior: "hidden"`, and `dependent_setting_display: "inline"`
|
|
so the category picker appears under the scope setting only when include/exclude modes need it.
|
|
|
|
Do not make `exclude` mean "public minus selected categories" unless the product requirement
|
|
explicitly says that. It is harder to explain and usually surprises admins.
|
|
|
|
Example from Discourse AI admin dashboard highlights:
|
|
|
|
```yaml
|
|
ai_admin_dashboard_highlights_category_scope:
|
|
type: enum
|
|
default: "public"
|
|
enum: "CategoryScopeSiteSetting"
|
|
client: false
|
|
area: "ai-features/admin_dashboard"
|
|
|
|
ai_admin_dashboard_highlights_categories:
|
|
type: category_list
|
|
default: ""
|
|
client: false
|
|
area: "ai-features/admin_dashboard"
|
|
depends_on:
|
|
- ai_admin_dashboard_highlights_category_scope
|
|
depends_on_values:
|
|
ai_admin_dashboard_highlights_category_scope:
|
|
- include
|
|
- include_strict
|
|
- exclude
|
|
- exclude_strict
|
|
depends_behavior: "hidden"
|
|
dependent_setting_display: "inline"
|
|
```
|
|
|
|
Recommended description shape:
|
|
|
|
```yaml
|
|
ai_admin_dashboard_highlights_category_scope: "Choose which categories AI highlights can use. Include and exclude options use {{setting:ai_admin_dashboard_highlights_categories}}. Personal messages are always excluded."
|
|
ai_admin_dashboard_highlights_categories: "Categories used when the AI highlights category scope is set to <strong>include</strong> or <strong>exclude</strong>."
|
|
```
|
|
|
|
Implementation notes:
|
|
|
|
- Use `Category.subcategory_ids(category_id)` when non-strict modes include descendants.
|
|
- Keep `IN (:category_ids)` / `NOT IN (:category_ids)` parameterized; parse category-list values
|
|
to integers before passing them into SQL.
|
|
- Handle empty lists explicitly:
|
|
- Empty include means match nothing.
|
|
- Empty exclude means exclude nothing.
|
|
- If results are cached, include both the scope and the configured category list in the cache key.
|
|
- Test public default, all, include, include strict, exclude, exclude strict, private categories,
|
|
personal messages if topics are involved, and subcategories.
|
|
|
|
### Enum Settings
|
|
|
|
- Prefer stable machine values such as `include_strict`; let descriptions/admin labels explain them.
|
|
- Keep enum values short, lowercase, and underscore-separated.
|
|
- When changing values before merge, update all tests and cache keys. After merge, treat value
|
|
changes as migrations/compatibility work.
|
|
|
|
### Dependent Settings
|
|
|
|
- Add a validator when one setting only works if another setting is enabled, configured, or has a
|
|
compatible value.
|
|
- Keep dependency enforcement server-side; admin descriptions are not enough.
|
|
- `depends_on` without `depends_on_values` is for boolean parent settings.
|
|
- Use `depends_on_values` when the parent is an enum/string setting and the dependent only applies
|
|
to specific values.
|
|
- Use `depends_behavior: "hidden"` when the dependent setting should be hidden until applicable.
|
|
- Use `dependent_setting_display: "inline"` when a hidden dependent belongs visually under its
|
|
parent; save/reset controls are grouped with the parent setting.
|
|
- Test both the valid and invalid combinations.
|
|
|
|
## Review Checklist
|
|
|
|
- Is the default safe for existing sites?
|
|
- Is the setting hidden from the client unless JS needs it?
|
|
- Does a dependency on another setting need a validator?
|
|
- Does the admin description answer the likely "what does this include?" question?
|
|
- Is the setting grouped into the right admin area?
|
|
- Was the admin settings page checked in the browser so the name and description render correctly?
|
|
- Are cache keys or background jobs affected by setting changes?
|
|
- Are private categories, personal messages, subcategories, deleted records, and anonymous access
|
|
handled where relevant?
|