1- App ``provides `` for Inter-App Data
2- ####################################
1+ App ``features `` for Inter-App Configuration
2+ ############################################
33
44Status
55======
@@ -11,15 +11,15 @@ Context
1111=======
1212
1313frontend-base applications currently communicate through two structured
14- mechanisms: ``routes `` and ``slots ``. Both are defined in the `` App `` interface
15- and consumed directly by frontend-base's runtime.
14+ mechanisms: ``routes ``, `` slots ``, and ``providers ``. All are defined in the
15+ `` App `` interface and consumed directly by frontend-base's runtime.
1616
17- As the platform evolves, however, situations arise where apps need to share data
18- with each other that frontend-base itself has no reason to understand. A
19- concrete example is the course navigation bar introduced in the header app.
20- The header needs to know two things from other apps:
17+ As the platform evolves, however, situations arise where apps need to share
18+ configuration data with each other that frontend-base itself has no reason to
19+ understand. A concrete example is the course navigation bar introduced in the
20+ header app. The header needs to know two things from other apps:
2121
22- 1. Which apps want the course navigation bar to appear (currently a hardcoded
22+ 1. Which apps want the course navigation bar to appear (previously a hardcoded
2323 list of roles in ``constants.ts ``).
2424
25252. Which URL patterns each app handles client-side, so the navigation bar can
@@ -36,14 +36,14 @@ runtime needs to interpret them directly. It builds a router from ``routes``
3636and renders widgets from ``slots ``. Any new field that frontend-base itself
3737must consume deserves the same treatment: a dedicated, typed field.
3838
39- But for data that flows between apps - where frontend-base is just the conduit -
40- a generic mechanism is more appropriate.
39+ But for generic configuration between apps - where frontend-base is just the
40+ conduit - a generic mechanism is more appropriate.
4141
4242
4343Decision
4444========
4545
46- Add an optional ``provides `` field to the ``App `` interface::
46+ Add an optional ``features `` field to the ``App `` interface::
4747
4848 export interface App {
4949 appId: string,
@@ -52,40 +52,39 @@ Add an optional ``provides`` field to the ``App`` interface::
5252 providers?: AppProvider[],
5353 slots?: SlotOperation[],
5454 config?: AppConfig,
55- provides ?: Record<string, unknown>,
55+ features ?: Record<string, unknown>,
5656 }
5757
58- ``provides `` is a flat key-value map where each key is an identifier agreed
59- upon by the providing and consuming apps, and the value is whatever the
60- consumer expects. frontend-base stores this data and exposes it through a
61- runtime function, but does not interpret it. Any namespaced identifier can
62- serve as a key.
58+ ``features `` is a flat key-value map where each key is a feature identifier
59+ agreed upon by the providing and consuming apps, and the value takes whatever
60+ shape the app that provides the feature expects. The runtime stores this data
61+ and exposes it through a runtime function, but does not interpret it. Any
62+ namespaced identifier can serve as a key.
6363
6464A runtime helper would look something like::
6565
66- // Returns all `provides ` entries matching the given key .
67- function getProvidedData(key : string): unknown[]
66+ // Returns all `features ` entries matching the given feature identifier .
67+ function getFeatureData(id : string): unknown[]
6868
6969
7070Guidelines
7171==========
7272
73- 1. ``provides `` is for inter-app data that frontend-base does not need to
74- interpret. If frontend-base's runtime must consume the data to function
75- (as it does with routes and slots), a dedicated typed field on ``App `` is
76- the right choice.
73+ 1. ``features `` is for inter-app configuration that the runtime does not need
74+ to interpret. If it must consume the data to function (as it does with
75+ routes and slots), a dedicated typed field on ``App `` is the right choice.
7776
78- 2. Keys in ``provides `` should be their own namespaced identifiers, not
77+ 2. Keys in ``features `` should be their own namespaced feature identifiers, not
7978 duplicates of existing app, slot, or widget IDs. This allows different
80- widgets or other entities to consume the same provided data independently,
79+ widgets or other entities to consume the same feature data independently,
8180 without coupling the data's identity to a single consumer.
8281
83823. The shape of the value under each key is a contract between the providing and
8483 consuming apps. It is not enforced by frontend-base. Consuming apps should
8584 validate or type-guard the data they receive.
8685
87- 4. ``provides `` should not be used as a back door to modify frontend-base 's
88- behavior. It is not a configuration mechanism for the runtime.
86+ 4. ``features `` should not be used as a back door to modify the runtime 's
87+ behavior. It is not a configuration mechanism for the runtime itself .
8988
9089
9190Consequences
@@ -96,7 +95,7 @@ frontend-base's ``App`` type or runtime for each new use case. The ``App``
9695interface grows by one optional field and remains stable as new inter-app
9796patterns emerge.
9897
99- The trade-off is that ``provides `` data is untyped from frontend-base's
98+ The trade-off is that ``features `` data is untyped from frontend-base's
10099perspective. Consuming apps bear the responsibility of defining, documenting,
101100and validating the shape of the data they expect. This is acceptable because
102101the data is, by definition, outside frontend-base's domain.
@@ -108,27 +107,28 @@ As a concrete illustration, the Instructor Dashboard app could declare::
108107
109108 const config: App = {
110109 appId: 'org.openedx.frontend.app.instructor',
111- provides : {
112- 'org.openedx.frontend.provides.courseNavigationRoles .v1': {
113- courseNavigationRoles: [ 'org.openedx.frontend.role.instructor'] ,
114- } ,
110+ features : {
111+ 'org.openedx.frontend.feature.showCourseNavigationBar .v1': [
112+ 'org.openedx.frontend.role.instructor',
113+ ] ,
115114 },
116115 routes: [...],
117116 slots: [...],
118117 };
119118
120- The header's course navigation bar widget collects ``provides `` entries keyed
121- to its provides identifier from all registered apps. From the provided roles
122- it determines both when to render the navigation bar (by checking
123- ``getActiveRoles() ``) and which tab URLs can be navigated client-side (by
124- resolving roles to route paths via ``getUrlByRouteRole() ``).
119+ The header's course navigation bar widget collects ``features `` entries keyed
120+ to its feature identifier from all registered apps. It expects the provided
121+ values to be role identifiers, from which it determines both when to render the
122+ navigation bar (by checking ``getActiveRoles() ``) and which tab URLs can be
123+ navigated client-side (by resolving roles to route paths via
124+ ``getUrlByRouteRole() ``).
125125
126126
127127Rejected alternatives
128128=====================
129129
130- Slot operations
131- ---------------
130+ Widget operations
131+ -----------------
132132
133133Each app could register its own widget into the course navigation bar slot
134134with an ``active `` condition on its role. The ``OPTIONS `` operation can even
@@ -155,5 +155,17 @@ static data. Each app would need a context, a provider component, and a
155155consumer hook, and the header would need to aggregate across multiple contexts
156156with no standard way to discover them. Providers are the right tool when data
157157changes over time and consumers need to re-render. The course navigation roles
158- are fixed at registration time and never change, making ``provides `` a more
158+ are fixed at registration time and never change, making ``features `` a more
159159natural fit.
160+
161+ Reusing ``App.config ``
162+ ----------------------
163+
164+ The existing ``App.config `` field has the same type (``Record<string, unknown> ``)
165+ and could theoretically hold feature data. However, ``config `` is per-app: it
166+ is retrieved by ``appId `` via ``getAppConfig() `` and is meant to hold settings
167+ *for * that app. ``features `` has a cross-app access pattern:
168+ ``getFeatureData() `` collects entries from all apps that declared data under a
169+ given feature identifier. Merging the two would require scanning every app's
170+ config for a specific key, blurring the distinction between settings an app
171+ consumes and data it exposes for others.
0 commit comments