Structure / Feature Plugin System (Code Level)
C4 Level 4 detail of the PSI frontend's core architectural pattern: how Structures define content types and Features extend them via config slots.
Core Concepts
classDiagram
class Structure {
+key: string
+name: string
+screens: Screen[]
+defaultConfig: Config
}
class Feature {
+key: string
+name: string
+config: Partial~Config~
+parentFeature: string?
}
class Config {
+widgets: Widget[]
+callbacks: Callback[]
+data: DataConfig
}
class Screen {
+key: string
+component: ReactComponent
}
class Widget {
+key: string
+priority: number
+component: ReactComponent
}
class Callback {
+key: string
+callback: Function
}
Structure "1" --> "*" Screen : defines
Structure "1" --> "1" Config : defaultConfig
Feature "*" --> "1" Structure : extends
Feature "1" --> "1" Config : partial config overlay
Config "1" --> "*" Widget : contains
Config "1" --> "*" Callback : contains Config Resolution Algorithm
When a structure instance is rendered, all enabled features are merged into the final configuration:
sequenceDiagram
participant Instance as Structure Instance
participant ConfigResolver as Config Resolver
participant Structure as Structure Definition
participant Feature1 as Feature: comment-slider
participant Feature2 as Feature: reactions
participant Feature3 as Feature: pin-comment
Instance->>ConfigResolver: Resolve config for this instance
ConfigResolver->>Structure: Get defaultConfig
Structure-->>ConfigResolver: Base config with empty slots
ConfigResolver->>Instance: Get enabled features list
Instance-->>ConfigResolver: [comment-slider, reactions, pin-comment]
ConfigResolver->>Feature1: Get config overlay
Feature1-->>ConfigResolver: {pageTopWidgets: [SliderWidget]}
ConfigResolver->>Feature2: Get config overlay
Feature2-->>ConfigResolver: {commentActionWidgets: [ReactionBar]}
ConfigResolver->>Feature3: Get config overlay
Feature3-->>ConfigResolver: {commentHeaderWidgets: [PinButton]}
Note over ConfigResolver: Merge rules:<br/>1. Arrays are APPENDED<br/>2. REPLACE() wrapper fully replaces<br/>3. Dedup by key, highest priority wins
ConfigResolver-->>Instance: Final merged config Config Slot Types
graph TD
subgraph "Config Slot Categories"
Widgets["Widget Slots<br/>(React components inserted into UI positions)"]
Callbacks["Callback Slots<br/>(Event handlers and post-processors)"]
Data["Data Slots<br/>(Labels, settings, rankers, data requirements)"]
end
subgraph "Widget Slot Examples"
PTW["pageTopWidgets: [CommentSlider, ContextEntryPoints]"]
CAW["commentActionWidgets: [ReactionBar, UpvoteButton]"]
CHW["commentHeaderWidgets: [PinButton]"]
CSW["composerSubWidgets: [PollCreator]"]
ABW["aboutBannerWidgets: [CommunityGuidelines]"]
end
subgraph "Callback Slot Examples"
CPC["commentPostCheckers: [checkModeration, checkLength]"]
CPP["commentPostProcessors: [translateContent]"]
OLA["onLoadActions: [loadRankings]"]
end
subgraph "Data Slot Examples"
RNK["rankers: [qualityRanker, bridgingRanker]"]
LBL["labels: {submitButton: 'Share', ...}"]
FLT["filters: [hideBlocked, hideSpam]"]
end
Widgets --> PTW
Widgets --> CAW
Widgets --> CHW
Widgets --> CSW
Widgets --> ABW
Callbacks --> CPC
Callbacks --> CPP
Callbacks --> OLA
Data --> RNK
Data --> LBL
Data --> FLT Merge Rules in Detail
graph TD
subgraph "Rule 1: Array Append"
A1["Structure default:<br/>pageTopWidgets: [A]"] --> M1["Feature adds:<br/>pageTopWidgets: [B]"]
M1 --> R1["Result:<br/>pageTopWidgets: [A, B]"]
end
subgraph "Rule 2: REPLACE Wrapper"
A2["Structure default:<br/>rankers: [defaultRanker]"] --> M2["Feature uses REPLACE:<br/>rankers: REPLACE([customRanker])"]
M2 --> R2["Result:<br/>rankers: [customRanker]<br/>(default is replaced entirely)"]
end
subgraph "Rule 3: Key Deduplication"
A3["Feature A adds:<br/>{key: 'submit', priority: 1, ...}"] --> M3["Feature B adds:<br/>{key: 'submit', priority: 10, ...}"]
M3 --> R3["Result: Feature B wins<br/>(higher priority)"]
end Structure Registry (All 18)
graph TD
subgraph "Multi-Instance Structures"
Question["Question<br/><em>A question users answer</em>"]
Profile["Profile<br/><em>User profile page</em>"]
Article["Article<br/><em>Content linked to questions/topics</em>"]
Topic["Topic<br/><em>Threaded discussion topic</em>"]
SimpleComments["Simple Comments<br/><em>Basic comment thread</em>"]
end
subgraph "Singleton Structures"
Admin["Admin<br/><em>Admin dashboard</em>"]
EditorialDash["Editorial Dash<br/><em>Create/browse questions</em>"]
ModerationDash["Moderation Dash<br/><em>Content moderation UI</em>"]
ModerationDashLegacy["Moderation Dash (Legacy)<br/><em>Old moderation interface</em>"]
Login["Login<br/><em>SSO provider selection</em>"]
Account["Account<br/><em>User's own account</em>"]
Settings["Settings<br/><em>User preferences</em>"]
DevTools["Developer Tools<br/><em>Debug and inspect</em>"]
ComponentDemo["Component Demo<br/><em>Design system showcase</em>"]
TestStructure["Test Structure<br/><em>Automated testing</em>"]
end
subgraph "ZDF Partner Structures"
ZDFModDash["ZDF Mod Dashboard<br/><em>ZDF-specific moderation</em>"]
VideoVoting["Video Voting<br/><em>Video-based voting UI</em>"]
end Feature Extension Example: Question Structure
graph TD
Q["Question Structure<br/>(defaultConfig)"]
Q --> CS["comment-slider<br/>Fills: pageTopWidgets<br/><em>Swipeable comment cards</em>"]
Q --> Reactions["reactions<br/>Fills: commentActionWidgets<br/><em>Emoji reactions</em>"]
Q --> Mod["moderation<br/>Fills: commentPostCheckers<br/><em>AI moderation gate</em>"]
Q --> Pin["pin-comment<br/>Fills: commentHeaderWidgets<br/><em>Pin to top</em>"]
Q --> Poll["poll-question<br/>Fills: composerSubWidgets<br/><em>Add polls to questions</em>"]
Q --> CEP["context-entry-points<br/>Fills: pageTopWidgets<br/><em>Links to related content</em>"]
Q --> RP["representing-perspectives<br/>Fills: pageTopWidgets<br/><em>Perspective visualization</em>"]
Q --> MP["most-popular<br/>Fills: data.rankers<br/><em>Popular comment sorting</em>"]
Q --> MA["multi-answer<br/>Fills: composerSubWidgets<br/><em>Multiple answers per user</em>"]
Q --> TP["thread-pagination<br/>Fills: data, callbacks<br/><em>Paginated comment loading</em>"]
Mod --> AIMod["sub: ai-moderation<br/><em>GPT-based auto-moderation</em>"]
Mod --> ManMod["sub: manual-moderation<br/><em>Human review queue</em>"] Feature Enablement Per Instance
graph TD
subgraph "Silo: ZDF"
I1["Question Instance 1<br/>Features: comment-slider, reactions, moderation, pin-comment"]
I2["Question Instance 2<br/>Features: poll-question, moderation, representing-perspectives"]
end
subgraph "Silo: CBC"
I3["Question Instance 3<br/>Features: multi-answer, moderation, thread-pagination"]
end
Note1["Same structure, different<br/>features per instance"]
subgraph "Feature Defaults"
SD["Silo defaults (psi.config.ts)<br/>Define which features are on by default"]
ID["Instance overrides<br/>global/features/{featureKey} = true/false"]
end
SD --> I1
SD --> I2
SD --> I3
ID --> I1
ID --> I2
ID --> I3 Further Reading
- Component View -- C4 Level 3 frontend components
- Design concepts (psi-product) -- full developer reference for structures, features, config
- Isolation Model -- how instance and module isolation works