@@ -21,94 +21,108 @@ Per ADR-022, template-derived apps own no database of their own — the Vue SPA
2121talks to OpenRegister's object API directly through a thin Pinia store layer.
2222This capability defines that store layer:
2323
24- - A ** generic object store** (` src/store/modules/object.js ` ) that is configured
25- once with the OpenRegister API base URLs, then accepts the registration of
26- named object types (each mapping a logical type name to a register + schema),
27- and fetches collections of those objects on demand.
24+ - A ** generic object store** obtained from ` createObjectStore ` in
25+ ` @conduction/nextcloud-vue ` and instantiated in ` src/store/store.js ` . The app
26+ does NOT define one of its own: per ADR-071 Decision 2 the library's factory
27+ is the only generic OpenRegister object store, and per ADR-026 an app-local
28+ copy drifts from the lib's action surface until a manifest-rendered page calls
29+ an action that no longer exists.
2830- An ** app-settings store** (` src/store/modules/settings.js ` ) that reads and
2931 writes the app's own settings through the backend ` GET ` /` POST /api/settings `
3032 endpoints defined by the settings-management capability — it is the frontend
3133 half of REQ-CFG-001 / REQ-CFG-002.
3234- A ** boot-time initializer** (` src/store/store.js ` ) that wires the object store
3335 to OpenRegister's URLs and primes the settings store before the SPA renders.
3436
35- All store actions MUST degrade gracefully: a failed network request MUST be
36- logged client-side and resolve to a safe empty/` null ` value so that a single
37- failing fetch never breaks the SPA mount (mirrors ADR-005's "log server-side,
38- return safe fallback" rule on the client).
37+ All store actions MUST degrade gracefully: a failed network request MUST NOT
38+ throw out of the action, so a single failing fetch never breaks the SPA mount
39+ (mirrors ADR-005's "log server-side, return safe fallback" rule on the client).
40+
41+ ** Degrading is not the same as going quiet.** An action that resolves to
42+ ` null ` /` [] ` on failure MUST also record the failure where a caller can see it —
43+ logged client-side AND exposed in store state. The earlier wording required only
44+ the safe value, and ` null ` was simultaneously the legitimate "nothing yet"
45+ value, so a settings endpoint returning 500 was indistinguishable from a fresh
46+ install for as long as anyone cared to look.
3947
4048## Requirements
4149
42- ### REQ-STORE-001: Configure the object store with OpenRegister URLs
50+ ### REQ-STORE-001: The object store comes from the library, not from the app
4351
44- The object store MUST expose a ` configure({ baseUrl, schemaBaseUrl }) ` action
45- that records the OpenRegister object-API and schema-API base URLs. No object
46- fetch may be attempted before the store has been configured.
52+ The app MUST obtain its generic OpenRegister object store from
53+ ` createObjectStore ` in ` @conduction/nextcloud-vue ` , instantiated once in
54+ ` src/store/store.js ` with the app's register and schema slugs. The app MUST NOT
55+ define its own generic object store.
4756
48- #### Scenario: Store is configured at boot
57+ Per ADR-071 Decision 2 the library's factory is the only generic object store in
58+ the fleet; per ADR-026 an app-local copy drifts from the lib's action surface,
59+ and the observed failure mode is a manifest-rendered page calling an action the
60+ local store never had (` fetchObject is not a function ` , decidesk #162 ).
4961
50- - GIVEN a freshly created object store with empty ` baseUrl `
51- - WHEN ` configure({ baseUrl, schemaBaseUrl }) ` is called with the OpenRegister object/schema API URLs
52- - THEN the store MUST persist ` baseUrl ` and ` schemaBaseUrl ` in its state
53- - AND subsequent ` fetchObjects ` calls MUST build their request URL from the stored ` baseUrl `
62+ #### Scenario: The store is created from the library factory
5463
55- ### REQ-STORE-002: Register named object types
64+ - GIVEN an app scaffolded from this template
65+ - WHEN ` src/store/store.js ` is loaded
66+ - THEN it MUST call ` createObjectStore ` imported from ` @conduction/nextcloud-vue `
67+ - AND it MUST pass the app's ` register ` and ` schema ` slugs
68+ - AND the module MUST export the resulting store as ` useObjectStore `
5669
57- The object store MUST expose a ` registerObjectType(type, schema, register) `
58- action that maps a logical type name to its OpenRegister ` schema ` + ` register `
59- identifiers and initialises an empty result bucket for that type. Fetching an
60- unregistered type MUST be a no-op that warns rather than throwing.
70+ #### Scenario: No app-local generic object store exists
6171
62- #### Scenario: A type is registered
72+ - GIVEN the app's ` src/store/ ` tree
73+ - WHEN it is searched for a Pinia store defining generic OpenRegister CRUD
74+ - THEN no module other than ` store.js ` MAY define one
75+ - AND in particular ` src/store/modules/object.js ` MUST NOT exist
6376
64- - GIVEN a configured object store
65- - WHEN ` registerObjectType('item', '<schemaId>', '<registerId>') ` is called
66- - THEN the store MUST record ` { schema, register } ` under ` objectTypes.item `
67- - AND it MUST initialise ` objects.item ` to an empty array if it was unset
77+ ### REQ-STORE-002: Object types are registered through the library's surface
6878
69- #### Scenario: Fetching an unregistered type
79+ Registering a logical type name against a register + schema, fetching
80+ collections, pagination, single-flight de-duplication and error surfacing are
81+ all the library store's responsibility. The app MUST use that surface rather
82+ than re-implementing any part of it.
7083
71- - GIVEN a configured store with no ` item ` type registered
72- - WHEN ` fetchObjects('item') ` is called
73- - THEN the store MUST emit a client-side warning naming the unregistered type
74- - AND it MUST return an empty array without performing a network request
84+ This requirement deliberately does not restate the library's action names. The
85+ previous version of this spec pinned ` configure() ` , ` registerObjectType() ` and
86+ ` fetchObjects() ` — an API the app's own dead module had and the library store
87+ does not — so the spec described a module nothing imported while the code used a
88+ different one.
7589
76- ### REQ-STORE-003: Fetch a collection of objects
90+ #### Scenario: The app needs a collection
7791
78- The object store MUST expose an async ` fetchObjects(type, params) ` action that
79- issues a ` GET ` to the configured ` baseUrl ` with the registered ` register ` and
80- ` schema ` as query parameters (plus any caller-supplied ` params ` ), carrying the
81- Nextcloud request token. On success it MUST store and return the result
82- collection; on any failure it MUST log client-side and return an empty array,
83- and it MUST clear the per-type loading flag in all cases.
92+ - GIVEN a store created by ` createObjectStore `
93+ - WHEN the app needs a collection of objects
94+ - THEN it MUST call the library store's own collection action
95+ - AND it MUST NOT construct the OpenRegister request URL itself
8496
85- #### Scenario: Successful fetch
97+ ### REQ-STORE-003: App code does not hand-set the request token
8698
87- - GIVEN a registered, configured ` item ` type
88- - WHEN ` fetchObjects('item', { limit: 10 }) ` resolves with HTTP 200
89- - THEN the request URL MUST carry ` register ` , ` schema ` , and ` limit=10 ` query parameters
90- - AND the request MUST send the ` requesttoken ` header
91- - AND the store MUST set ` objects.item ` to ` data.results ` (or ` data ` when no ` results ` envelope) and return it
92- - AND ` loading.item ` MUST be cleared to ` false `
99+ Any HTTP the app performs outside the object store MUST go through ` cnFetch ` or
100+ ` cnFetchJson ` from ` @conduction/nextcloud-vue ` . A raw ` fetch() ` carrying a
101+ hand-set ` requesttoken ` header is forbidden (ADR-071 Decision 1): the library
102+ owns the one blessed CSRF idiom, URL prefixing and error normalisation, so a
103+ Nextcloud-version or CSP change is one fix rather than one per app.
93104
94- #### Scenario: Network failure
105+ #### Scenario: The settings store reads the backend
95106
96- - GIVEN a registered type
97- - WHEN the fetch rejects or returns a non-OK status
98- - THEN the store MUST log the error client-side
99- - AND it MUST return an empty array
100- - AND ` loading.item ` MUST still be cleared to ` false `
107+ - GIVEN the settings store issues ` GET /api/settings `
108+ - WHEN the request is constructed
109+ - THEN it MUST be issued through ` cnFetchJson `
110+ - AND the app MUST NOT import ` getRequestToken ` to build the header itself
111+ - AND the URL MUST be resolved with ` generateUrl ` so an instance served from a
112+ webroot subdirectory is addressed correctly
101113
102114### REQ-STORE-004: Read and write app settings from the SPA
103115
104116The settings store MUST expose an async ` fetchSettings() ` action that ` GET ` s
105117` /api/settings ` and a ` saveSettings(payload) ` action that ` POST ` s a partial
106118settings payload to the same endpoint, both carrying the request token. These
107119are the client counterparts of the settings-management capability's REQ-CFG-001
108- and REQ-CFG-002. ` fetchSettings() ` MUST additionally derive the
120+ and REQ-CFG-002. Both actions MUST be issued through ` cnFetchJson `
121+ (REQ-STORE-003). ` fetchSettings() ` MUST additionally derive the
109122` hasOpenRegisters ` and ` isAdmin ` flags from the response so the UI can degrade
110- gracefully (ADR-005 / REQ-CFG-004). Both actions MUST log and return ` null ` on
111- failure rather than throwing.
123+ gracefully (ADR-005 / REQ-CFG-004). Both actions MUST return ` null ` on failure
124+ rather than throwing, and MUST record the failure in the store's ` error ` state
125+ so that "the request failed" is distinguishable from "there is nothing yet".
112126
113127#### Scenario: Settings load
114128
@@ -131,18 +145,31 @@ failure rather than throwing.
131145- WHEN the action handles the failure
132146- THEN it MUST log the error client-side
133147- AND it MUST return ` null `
148+ - AND it MUST set ` error ` to the failure
134149- AND ` loading ` MUST be reset to ` false `
135150
151+ #### Scenario: A failure is distinguishable from an empty result
152+
153+ - GIVEN ` fetchSettings() ` has returned ` null `
154+ - WHEN a caller needs to know whether the backend failed or simply had nothing
155+ - THEN ` error ` MUST be non-null if and only if the request failed
156+ - AND ` error ` MUST be cleared at the start of the next request
157+
136158### REQ-STORE-005: Initialise the stores before the SPA renders
137159
138160The system MUST expose an async ` initializeStores() ` boot helper that
139- configures the object store to point at OpenRegister's object/schema API URLs
140- and primes the settings store with a first ` fetchSettings() ` call, returning
141- both store handles to the caller.
161+ instantiates the object store and primes the settings store with a first
162+ ` fetchSettings() ` call, returning both store handles to the caller.
163+
164+ The library store is configured by the ` register ` /` schema ` passed to
165+ ` createObjectStore ` at module load, so there is no separate ` configure() ` step.
166+ The previous wording required one, describing the app-local store this template
167+ no longer ships.
142168
143169#### Scenario: Boot sequence
144170
145171- WHEN ` initializeStores() ` is awaited during SPA bootstrap
146- - THEN it MUST call ` objectStore.configure() ` with the OpenRegister ` /api/objects ` and ` /api/schemas ` URLs
172+ - THEN it MUST instantiate the object store created by ` createObjectStore `
147173- AND it MUST await ` settingsStore.fetchSettings() ` so the first render has settings available
148174- AND it MUST return ` { settingsStore, objectStore } `
175+ - AND it MUST NOT fail the boot when ` fetchSettings() ` resolves to ` null `
0 commit comments