Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 222 additions & 50 deletions openspec/specs/admin-settings/spec.md

Large diffs are not rendered by default.

264 changes: 200 additions & 64 deletions openspec/specs/css-architecture/spec.md

Large diffs are not rendered by default.

169 changes: 137 additions & 32 deletions openspec/specs/hide-slogan/spec.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
---
status: reviewed
status: enriched
reviewed_date: 2026-02-28
enriched_date: 2026-03-20
---

# Hide Slogan Specification

## Purpose
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.
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.

## Requirements

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

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

#### Scenario: Setting persists across app restarts
- GIVEN the admin has enabled the hide slogan setting
- WHEN the Nextcloud server is restarted
- THEN the setting MUST still be `'1'` in IConfig
- AND the slogan MUST remain hidden on the login page

### REQ-SLGN-002: Conditional CSS Loading
The hide-slogan CSS file MUST only be loaded when the feature is enabled.
The hide-slogan CSS file MUST only be loaded when the feature is enabled, minimizing unnecessary CSS injection.

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

#### Scenario: Feature disabled skips CSS
- GIVEN `IConfig` returns `'0'` for `hide_slogan`
- WHEN `Application::injectThemeCSS()` runs during boot
- THEN `hide-slogan` CSS MUST NOT be loaded
- AND no slogan-hiding styles MUST be injected into the page

#### Scenario: CSS loading position in cascade
- GIVEN the hide-slogan CSS is loaded
- WHEN the CSS cascade is evaluated
- THEN hide-slogan MUST load after Layer 7 (element-overrides) and custom-overrides
- AND before any user-agent default styles could interfere
- AND the `!important` declarations MUST ensure the hiding takes effect regardless of other styles

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

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

#### Scenario: Multiple selector coverage
#### Scenario: Multiple selector coverage for robustness
- GIVEN the hide-slogan CSS is loaded
- WHEN the login page renders
- THEN the CSS MUST target these selectors for maximum coverage:
- `footer.guest-box`
- `#body-login footer.guest-box`
- `body.body-login-container footer.guest-box`
- `footer.guest-box` (direct match)
- `#body-login footer.guest-box` (login page context)
- `body.body-login-container footer.guest-box` (container class context)
- AND all three selectors MUST apply the same `display: none !important` and `visibility: hidden !important`

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

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

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

#### Scenario: Other guest-box elements unaffected
- GIVEN the hide-slogan CSS is loaded
- AND the login page has a main `.guest-box` element (the login form)
- WHEN the page renders
- THEN only the `footer.guest-box` element MUST be hidden
- AND the main `div.guest-box` or other non-footer guest-box elements MUST remain visible

#### Scenario: Login page layout preserved
- GIVEN the slogan footer is hidden
- WHEN the login page layout is computed
- THEN `display: none` MUST remove the element from the layout flow
- AND no empty space MUST remain where the slogan was
- AND the login form vertical centering MUST remain correct

### REQ-SLGN-005: Boolean Conversion
The controller MUST correctly convert the boolean API parameter to a string for IConfig storage.
The controller MUST correctly convert the boolean API parameter to a string for IConfig storage, using strict type comparison.

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

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

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

### REQ-SLGN-006: API Endpoint
The app MUST expose an admin-only API endpoint for toggling the hide slogan setting.
Expand All @@ -117,38 +150,110 @@ The app MUST expose an admin-only API endpoint for toggling the hide slogan sett
- GIVEN a non-admin user is authenticated
- WHEN `POST /apps/nldesign/settings/slogan` is called
- THEN the request MUST be rejected by the `@AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)` annotation
- AND the setting MUST NOT be modified

#### Scenario: Route registration
- GIVEN the app's routes configuration
- WHEN routes are loaded from `appinfo/routes.php`
- THEN a POST route for `/settings/slogan` MUST be mapped to `settings#setSloganSetting`

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

#### Scenario: Both hiding mechanisms applied
- GIVEN the hide-slogan CSS is loaded
- WHEN the selectors are processed
- THEN `display: none !important` MUST be set (removes from layout flow)
- AND `visibility: hidden !important` MUST be set (ensures no visual trace)
- AND both properties MUST use `!important` to override any Nextcloud styles
- THEN `display: none !important` MUST be set (removes from layout flow and accessibility tree)
- AND `visibility: hidden !important` MUST be set (ensures no visual trace in edge cases)
- AND both properties MUST use `!important` to override any Nextcloud core styles or third-party theme styles

#### Scenario: Accessibility tree impact
- GIVEN the slogan footer is hidden with `display: none`
- WHEN a screen reader traverses the login page
- THEN the slogan text MUST NOT be announced
- AND this is acceptable because the slogan is decorative, not functional content

#### Scenario: Print stylesheet compatibility
- GIVEN the slogan is hidden on screen
- WHEN the login page is printed
- THEN the slogan MUST also be hidden in print (because `display: none !important` applies to all media)

### REQ-SLGN-008: Admin Settings Panel Integration
The hide slogan checkbox in the admin settings MUST reflect and control the current state.

#### Scenario: Checkbox reflects current state on load
- GIVEN the hide slogan setting is enabled
- WHEN the settings panel loads
- THEN the `#nldesign-hide-slogan` checkbox MUST be checked
- AND the checkbox MUST have `class="checkbox"` for Nextcloud form styling

#### Scenario: Checkbox change triggers save
- GIVEN the admin unchecks the hide slogan checkbox
- WHEN the change event fires in JavaScript
- THEN `POST /apps/nldesign/settings/slogan` MUST be called with `hideSlogan=false`
- AND on success, the change takes effect on next page load (CSS is injected at boot time)

#### Scenario: Checkbox label is localized
- GIVEN the settings panel renders
- THEN the checkbox label MUST read "Hide Nextcloud slogan/payoff on login page" (via `$l->t()`)
- AND the label MUST be linked to the checkbox via `for="nldesign-hide-slogan"`

### REQ-SLGN-009: Government Branding Compliance
The hide slogan feature MUST support Dutch government branding requirements for clean login pages.

#### Scenario: Rijkshuisstijl login page compliance
- GIVEN the rijkshuisstijl token set is active
- AND the hide slogan feature is enabled
- WHEN the login page renders
- THEN no Nextcloud branding text MUST appear below the login form
- AND the login page MUST present only the government organization's branding
- AND the clean appearance MUST align with Rijkshuisstijl guidelines

#### Scenario: Municipality login page compliance
- GIVEN a gemeente token set (e.g., amsterdam) is active
- AND the hide slogan feature is enabled
- WHEN the login page renders
- THEN the municipality's visual identity MUST be the sole branding on the page
- AND the Nextcloud slogan MUST NOT distract from the government branding

#### Scenario: Feature works with all token sets
- GIVEN any token set is active (including stock Nextcloud)
- WHEN the hide slogan feature is enabled
- THEN the slogan MUST be hidden regardless of which token set is selected
- AND the feature MUST function independently of the token set choice

### REQ-SLGN-010: Effect Requires Page Reload
The hide slogan setting takes effect at boot time (CSS injection), so changes MUST take effect on the next page load.

#### Scenario: Setting change not immediate
- GIVEN the admin enables hide slogan in the settings panel
- WHEN the API call succeeds
- THEN the current page MUST NOT immediately hide the slogan on the login page
- AND the CSS MUST be injected on the next full page load via `Application::boot()`

#### Scenario: Admin sees effect by navigating to login page
- GIVEN the admin has enabled the hide slogan setting
- WHEN the admin opens the login page in a new tab or incognito window
- THEN the slogan MUST be hidden
- AND this confirms the setting is active

### Current Implementation Status

**Fully implemented:**
- Configuration storage: `Application.php` reads `hide_slogan` from `IConfig` with default `'0'`, compares with `=== '1'` (line 80)
- API endpoint: `POST /apps/nldesign/settings/slogan` mapped in `appinfo/routes.php` (line 10) to `SettingsController::setSloganSetting()` (`lib/Controller/SettingsController.php` lines 161-175)
- Boolean conversion: `setSloganSetting(bool $hideSlogan)` uses strict `=== true` to convert to `'1'`/`'0'` string (lines 163-166)
- Configuration storage: `Application.php` reads `hide_slogan` from `IConfig` with default `'0'`, compares with `=== '1'` (line 85)
- API endpoint: `POST /apps/nldesign/settings/slogan` mapped in `appinfo/routes.php` (line 14) to `SettingsController::setSloganSetting()`
- Boolean conversion: `saveBooleanSetting('hide_slogan', $hideSlogan)` uses strict `=== true` to convert to `'1'`/`'0'` string (`lib/Controller/SettingsController.php` lines 162-170)
- Conditional CSS loading: `Application::injectThemeCSS()` loads `hide-slogan` CSS only when `$hideSlogan === true` (lines 112-114)
- 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`
- 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`
- Admin-only access: `@AuthorizedAdminSetting(settings=OCA\NLDesign\Settings\Admin)` annotation on `setSloganSetting()`
- Settings panel checkbox: `templates/settings/admin.php` renders `#nldesign-hide-slogan` checkbox with correct checked state and label text
- JavaScript handler: `js/admin.js` calls `saveSloganSetting()` on checkbox change via `POST /apps/nldesign/settings/slogan`
- Settings panel checkbox: `templates/settings/admin.php` renders `#nldesign-hide-slogan` checkbox with correct checked state and localized label text
- JavaScript handler: `js/admin.js` calls save on checkbox change via `POST /apps/nldesign/settings/slogan`

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

### Standards & References
- Rijkshuisstijl guidelines: Dutch government login pages should present clean, branded appearance without third-party slogans
- WCAG AA: hiding decorative text does not affect accessibility; the `display: none` approach correctly removes elements from the accessibility tree
- 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)
- Nextcloud login page structure: `footer.guest-box` is the standard container for the slogan on guest/login pages

### Specificity Assessment
- This spec is highly specific and directly implementable. Every scenario maps 1:1 to the implementation.
- All CSS selectors, IConfig keys, API endpoints, boolean conversion logic, and conditional loading behavior are precisely defined.
- No ambiguities or open questions remain -- this spec is complete as-is.
- OWASP: admin-only endpoint protects against unauthorized setting changes
Loading
Loading