Skip to content

Commit 3937623

Browse files
committed
feat: Enrich all 7 NLDesign theming specs with deep research
1 parent 7f122b6 commit 3937623

7 files changed

Lines changed: 1423 additions & 312 deletions

File tree

openspec/specs/admin-settings/spec.md

Lines changed: 222 additions & 50 deletions
Large diffs are not rendered by default.

openspec/specs/css-architecture/spec.md

Lines changed: 200 additions & 64 deletions
Large diffs are not rendered by default.

openspec/specs/hide-slogan/spec.md

Lines changed: 137 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
---
2-
status: reviewed
2+
status: enriched
33
reviewed_date: 2026-02-28
4+
enriched_date: 2026-03-20
45
---
56

67
# Hide Slogan Specification
78

89
## Purpose
9-
Defines the "Hide Slogan" feature that removes the Nextcloud slogan/payoff text from the login page. Dutch government organizations typically need to present a clean, branded login page without Nextcloud's default slogan ("a safe home for all your data"). When enabled, the footer element on the login page that contains this slogan is completely hidden.
10+
Defines the "Hide Slogan" feature that removes the Nextcloud slogan/payoff text from the login page. Dutch government organizations typically need to present a clean, branded login page without Nextcloud's default slogan ("a safe home for all your data"). When enabled, the footer element on the login page that contains this slogan is completely hidden via a conditionally loaded CSS file.
1011

1112
## Requirements
1213

1314
### REQ-SLGN-001: Configuration Storage
14-
The hide slogan setting MUST be stored in Nextcloud's `IConfig` as a string value.
15+
The hide slogan setting MUST be stored in Nextcloud's `IConfig` as a string value with clear on/off semantics.
1516

1617
#### Scenario: Setting stored as enabled
1718
- GIVEN the admin enables the hide slogan feature
@@ -31,45 +32,61 @@ The hide slogan setting MUST be stored in Nextcloud's `IConfig` as a string valu
3132
- THEN the default value MUST be `'0'` (disabled)
3233
- AND the slogan MUST be visible on the login page
3334

35+
#### Scenario: Setting persists across app restarts
36+
- GIVEN the admin has enabled the hide slogan setting
37+
- WHEN the Nextcloud server is restarted
38+
- THEN the setting MUST still be `'1'` in IConfig
39+
- AND the slogan MUST remain hidden on the login page
40+
3441
### REQ-SLGN-002: Conditional CSS Loading
35-
The hide-slogan CSS file MUST only be loaded when the feature is enabled.
42+
The hide-slogan CSS file MUST only be loaded when the feature is enabled, minimizing unnecessary CSS injection.
3643

3744
#### Scenario: Feature enabled loads CSS
3845
- GIVEN `IConfig` returns `'1'` for `hide_slogan`
3946
- WHEN `Application::injectThemeCSS()` runs during boot
4047
- THEN `\OCP\Util::addStyle('nldesign', 'hide-slogan')` MUST be called
41-
- AND the CSS file MUST be loaded after the 7 core CSS layers
48+
- AND the CSS file MUST be loaded after all core CSS layers and the custom-overrides layer
4249

4350
#### Scenario: Feature disabled skips CSS
4451
- GIVEN `IConfig` returns `'0'` for `hide_slogan`
4552
- WHEN `Application::injectThemeCSS()` runs during boot
4653
- THEN `hide-slogan` CSS MUST NOT be loaded
54+
- AND no slogan-hiding styles MUST be injected into the page
55+
56+
#### Scenario: CSS loading position in cascade
57+
- GIVEN the hide-slogan CSS is loaded
58+
- WHEN the CSS cascade is evaluated
59+
- THEN hide-slogan MUST load after Layer 7 (element-overrides) and custom-overrides
60+
- AND before any user-agent default styles could interfere
61+
- AND the `!important` declarations MUST ensure the hiding takes effect regardless of other styles
4762

4863
### REQ-SLGN-003: Slogan Element Hiding
49-
When the feature is enabled, the login page footer containing the slogan MUST be completely hidden.
64+
When the feature is enabled, the login page footer containing the slogan MUST be completely hidden from both visual display and the accessibility tree.
5065

5166
#### Scenario: Footer element hidden with display none
5267
- GIVEN the hide-slogan CSS is loaded
5368
- WHEN the login page renders
5469
- THEN `footer.guest-box` MUST have `display: none !important`
5570
- AND `visibility: hidden !important`
5671

57-
#### Scenario: Multiple selector coverage
72+
#### Scenario: Multiple selector coverage for robustness
5873
- GIVEN the hide-slogan CSS is loaded
5974
- WHEN the login page renders
6075
- THEN the CSS MUST target these selectors for maximum coverage:
61-
- `footer.guest-box`
62-
- `#body-login footer.guest-box`
63-
- `body.body-login-container footer.guest-box`
76+
- `footer.guest-box` (direct match)
77+
- `#body-login footer.guest-box` (login page context)
78+
- `body.body-login-container footer.guest-box` (container class context)
79+
- AND all three selectors MUST apply the same `display: none !important` and `visibility: hidden !important`
6480

6581
#### Scenario: Slogan visible when feature disabled
6682
- GIVEN the hide-slogan CSS is NOT loaded
6783
- WHEN the login page renders
6884
- THEN the `footer.guest-box` element MUST display normally
6985
- AND the Nextcloud slogan/payoff text MUST be visible
86+
- AND no residual hiding styles MUST affect the footer
7087

71-
### REQ-SLGN-004: Login Page Only
72-
The hide slogan CSS MUST only affect the login page footer and MUST NOT affect other footer elements.
88+
### REQ-SLGN-004: Login Page Only Scope
89+
The hide slogan CSS MUST only affect the login page footer and MUST NOT affect other footer elements or pages.
7390

7491
#### Scenario: Non-login page footers unaffected
7592
- GIVEN the hide-slogan CSS is loaded
@@ -78,25 +95,41 @@ The hide slogan CSS MUST only affect the login page footer and MUST NOT affect o
7895
- THEN no footer elements MUST be hidden
7996
- AND the selectors MUST be specific to `.guest-box` footer elements (only present on login/guest pages)
8097

98+
#### Scenario: Other guest-box elements unaffected
99+
- GIVEN the hide-slogan CSS is loaded
100+
- AND the login page has a main `.guest-box` element (the login form)
101+
- WHEN the page renders
102+
- THEN only the `footer.guest-box` element MUST be hidden
103+
- AND the main `div.guest-box` or other non-footer guest-box elements MUST remain visible
104+
105+
#### Scenario: Login page layout preserved
106+
- GIVEN the slogan footer is hidden
107+
- WHEN the login page layout is computed
108+
- THEN `display: none` MUST remove the element from the layout flow
109+
- AND no empty space MUST remain where the slogan was
110+
- AND the login form vertical centering MUST remain correct
111+
81112
### REQ-SLGN-005: Boolean Conversion
82-
The controller MUST correctly convert the boolean API parameter to a string for IConfig storage.
113+
The controller MUST correctly convert the boolean API parameter to a string for IConfig storage, using strict type comparison.
83114

84115
#### Scenario: True boolean converted to string '1'
85116
- GIVEN the API receives `hideSlogan` as boolean `true`
86117
- WHEN `setSloganSetting(true)` is called
87-
- THEN the value stored in IConfig MUST be the string `'1'`
118+
- THEN `saveBooleanSetting('hide_slogan', true)` MUST be called
88119
- AND the comparison MUST use strict equality (`=== true`)
120+
- AND the value stored in IConfig MUST be the string `'1'`
89121

90122
#### Scenario: False boolean converted to string '0'
91123
- GIVEN the API receives `hideSlogan` as boolean `false`
92124
- WHEN `setSloganSetting(false)` is called
93-
- THEN the value stored in IConfig MUST be the string `'0'`
125+
- THEN `saveBooleanSetting('hide_slogan', false)` MUST be called
126+
- AND the value stored in IConfig MUST be the string `'0'`
94127

95128
#### Scenario: Boot phase reads and compares correctly
96129
- GIVEN `IConfig` stores `'1'` for `hide_slogan`
97130
- WHEN `Application::injectThemeCSS()` reads the value
98131
- THEN it MUST compare with `=== '1'` to get boolean `true`
99-
- AND it MUST NOT use loose comparison that could match other truthy values
132+
- AND it MUST NOT use loose comparison that could match other truthy values (e.g., `'yes'`, `'true'`, `1`)
100133

101134
### REQ-SLGN-006: API Endpoint
102135
The app MUST expose an admin-only API endpoint for toggling the hide slogan setting.
@@ -117,38 +150,110 @@ The app MUST expose an admin-only API endpoint for toggling the hide slogan sett
117150
- GIVEN a non-admin user is authenticated
118151
- WHEN `POST /apps/nldesign/settings/slogan` is called
119152
- THEN the request MUST be rejected by the `@AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)` annotation
153+
- AND the setting MUST NOT be modified
154+
155+
#### Scenario: Route registration
156+
- GIVEN the app's routes configuration
157+
- WHEN routes are loaded from `appinfo/routes.php`
158+
- THEN a POST route for `/settings/slogan` MUST be mapped to `settings#setSloganSetting`
120159

121160
### REQ-SLGN-007: Dual Hiding Strategy
122-
The hide slogan CSS MUST use both `display: none` and `visibility: hidden` to ensure complete removal.
161+
The hide slogan CSS MUST use both `display: none` and `visibility: hidden` to ensure complete removal across different rendering contexts.
123162

124163
#### Scenario: Both hiding mechanisms applied
125164
- GIVEN the hide-slogan CSS is loaded
126165
- WHEN the selectors are processed
127-
- THEN `display: none !important` MUST be set (removes from layout flow)
128-
- AND `visibility: hidden !important` MUST be set (ensures no visual trace)
129-
- AND both properties MUST use `!important` to override any Nextcloud styles
166+
- THEN `display: none !important` MUST be set (removes from layout flow and accessibility tree)
167+
- AND `visibility: hidden !important` MUST be set (ensures no visual trace in edge cases)
168+
- AND both properties MUST use `!important` to override any Nextcloud core styles or third-party theme styles
169+
170+
#### Scenario: Accessibility tree impact
171+
- GIVEN the slogan footer is hidden with `display: none`
172+
- WHEN a screen reader traverses the login page
173+
- THEN the slogan text MUST NOT be announced
174+
- AND this is acceptable because the slogan is decorative, not functional content
175+
176+
#### Scenario: Print stylesheet compatibility
177+
- GIVEN the slogan is hidden on screen
178+
- WHEN the login page is printed
179+
- THEN the slogan MUST also be hidden in print (because `display: none !important` applies to all media)
180+
181+
### REQ-SLGN-008: Admin Settings Panel Integration
182+
The hide slogan checkbox in the admin settings MUST reflect and control the current state.
183+
184+
#### Scenario: Checkbox reflects current state on load
185+
- GIVEN the hide slogan setting is enabled
186+
- WHEN the settings panel loads
187+
- THEN the `#nldesign-hide-slogan` checkbox MUST be checked
188+
- AND the checkbox MUST have `class="checkbox"` for Nextcloud form styling
189+
190+
#### Scenario: Checkbox change triggers save
191+
- GIVEN the admin unchecks the hide slogan checkbox
192+
- WHEN the change event fires in JavaScript
193+
- THEN `POST /apps/nldesign/settings/slogan` MUST be called with `hideSlogan=false`
194+
- AND on success, the change takes effect on next page load (CSS is injected at boot time)
195+
196+
#### Scenario: Checkbox label is localized
197+
- GIVEN the settings panel renders
198+
- THEN the checkbox label MUST read "Hide Nextcloud slogan/payoff on login page" (via `$l->t()`)
199+
- AND the label MUST be linked to the checkbox via `for="nldesign-hide-slogan"`
200+
201+
### REQ-SLGN-009: Government Branding Compliance
202+
The hide slogan feature MUST support Dutch government branding requirements for clean login pages.
203+
204+
#### Scenario: Rijkshuisstijl login page compliance
205+
- GIVEN the rijkshuisstijl token set is active
206+
- AND the hide slogan feature is enabled
207+
- WHEN the login page renders
208+
- THEN no Nextcloud branding text MUST appear below the login form
209+
- AND the login page MUST present only the government organization's branding
210+
- AND the clean appearance MUST align with Rijkshuisstijl guidelines
211+
212+
#### Scenario: Municipality login page compliance
213+
- GIVEN a gemeente token set (e.g., amsterdam) is active
214+
- AND the hide slogan feature is enabled
215+
- WHEN the login page renders
216+
- THEN the municipality's visual identity MUST be the sole branding on the page
217+
- AND the Nextcloud slogan MUST NOT distract from the government branding
218+
219+
#### Scenario: Feature works with all token sets
220+
- GIVEN any token set is active (including stock Nextcloud)
221+
- WHEN the hide slogan feature is enabled
222+
- THEN the slogan MUST be hidden regardless of which token set is selected
223+
- AND the feature MUST function independently of the token set choice
224+
225+
### REQ-SLGN-010: Effect Requires Page Reload
226+
The hide slogan setting takes effect at boot time (CSS injection), so changes MUST take effect on the next page load.
227+
228+
#### Scenario: Setting change not immediate
229+
- GIVEN the admin enables hide slogan in the settings panel
230+
- WHEN the API call succeeds
231+
- THEN the current page MUST NOT immediately hide the slogan on the login page
232+
- AND the CSS MUST be injected on the next full page load via `Application::boot()`
233+
234+
#### Scenario: Admin sees effect by navigating to login page
235+
- GIVEN the admin has enabled the hide slogan setting
236+
- WHEN the admin opens the login page in a new tab or incognito window
237+
- THEN the slogan MUST be hidden
238+
- AND this confirms the setting is active
130239

131240
### Current Implementation Status
132241

133242
**Fully implemented:**
134-
- Configuration storage: `Application.php` reads `hide_slogan` from `IConfig` with default `'0'`, compares with `=== '1'` (line 80)
135-
- API endpoint: `POST /apps/nldesign/settings/slogan` mapped in `appinfo/routes.php` (line 10) to `SettingsController::setSloganSetting()` (`lib/Controller/SettingsController.php` lines 161-175)
136-
- Boolean conversion: `setSloganSetting(bool $hideSlogan)` uses strict `=== true` to convert to `'1'`/`'0'` string (lines 163-166)
243+
- Configuration storage: `Application.php` reads `hide_slogan` from `IConfig` with default `'0'`, compares with `=== '1'` (line 85)
244+
- API endpoint: `POST /apps/nldesign/settings/slogan` mapped in `appinfo/routes.php` (line 14) to `SettingsController::setSloganSetting()`
245+
- Boolean conversion: `saveBooleanSetting('hide_slogan', $hideSlogan)` uses strict `=== true` to convert to `'1'`/`'0'` string (`lib/Controller/SettingsController.php` lines 162-170)
137246
- Conditional CSS loading: `Application::injectThemeCSS()` loads `hide-slogan` CSS only when `$hideSlogan === true` (lines 112-114)
138-
- CSS file: `css/hide-slogan.css` (17 lines) targets `footer.guest-box`, `#body-login footer.guest-box`, and `body.body-login-container footer.guest-box` with both `display: none !important` and `visibility: hidden !important`
247+
- CSS file: `css/hide-slogan.css` targets `footer.guest-box`, `#body-login footer.guest-box`, and `body.body-login-container footer.guest-box` with both `display: none !important` and `visibility: hidden !important`
139248
- Admin-only access: `@AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)` annotation on `setSloganSetting()`
140-
- Settings panel checkbox: `templates/settings/admin.php` renders `#nldesign-hide-slogan` checkbox with correct checked state and label text
141-
- JavaScript handler: `js/admin.js` calls `saveSloganSetting()` on checkbox change via `POST /apps/nldesign/settings/slogan`
249+
- Settings panel checkbox: `templates/settings/admin.php` renders `#nldesign-hide-slogan` checkbox with correct checked state and localized label text
250+
- JavaScript handler: `js/admin.js` calls save on checkbox change via `POST /apps/nldesign/settings/slogan`
142251

143252
**Not yet implemented:**
144253
- All requirements in this spec are fully implemented.
145254

146255
### Standards & References
147256
- Rijkshuisstijl guidelines: Dutch government login pages should present clean, branded appearance without third-party slogans
148-
- WCAG AA: hiding decorative text does not affect accessibility; the `display: none` approach correctly removes elements from the accessibility tree
257+
- WCAG 2.1 AA: hiding decorative text with `display: none` correctly removes elements from the accessibility tree (this is acceptable for non-functional slogan text)
149258
- Nextcloud login page structure: `footer.guest-box` is the standard container for the slogan on guest/login pages
150-
151-
### Specificity Assessment
152-
- This spec is highly specific and directly implementable. Every scenario maps 1:1 to the implementation.
153-
- All CSS selectors, IConfig keys, API endpoints, boolean conversion logic, and conditional loading behavior are precisely defined.
154-
- No ambiguities or open questions remain -- this spec is complete as-is.
259+
- OWASP: admin-only endpoint protects against unauthorized setting changes

0 commit comments

Comments
 (0)