From f8297f43fbe3dd73ab9bcabb8b714bf372c5ece5 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 19 Mar 2026 19:44:41 +0100 Subject: [PATCH 01/10] chore: migrate docusaurus/ to docs/ and standardize workflow Move all Docusaurus config, src, and static files from docusaurus/ into docs/. Switch to the company-wide reusable documentation workflow. Update editUrl reference accordingly. --- .phpunit.cache/test-results | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpunit.cache/test-results diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index 9c6c18b..0000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":2,"defects":[],"times":{"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testGetTokensReturnsNonEmptyArray":0.016,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testEveryTokenHasRequiredKeys":0.005,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testTokenNamesStartWithDoubleDash":0.002,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testTokenTypesAreValid":0.002,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testTokenTabsAreValid":0.002,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testGetTabLabelsCoversAllTabs":0.001,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testGetTokenNamesMatchesGetTokensKeys":0.002,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testIsEditableReturnsTrueForKnownToken":0.001,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testIsEditableReturnsFalseForUnknownToken":0.001,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testGetTokensByTabGroupsCorrectly":0.003,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testGetTokensByTabPreservesAllTokens":0,"OCA\\NLDesign\\Tests\\Unit\\Service\\TokenRegistryTest::testColorPrimaryIsInLoginTab":0.001}} \ No newline at end of file From 5c201187aa679900ee7748267b939f900897b2b9 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 19 Mar 2026 19:45:44 +0100 Subject: [PATCH 02/10] fix: update editUrl to reference docs/ instead of docusaurus/ --- docs/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index fbf914c..e4bcd38 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -29,7 +29,7 @@ const config = { path: './', sidebarPath: require.resolve('./sidebars.js'), editUrl: - 'https://github.com/ConductionNL/nldesign/tree/main/docusaurus/', + 'https://github.com/ConductionNL/nldesign/tree/main/docs/', }, blog: false, theme: { From 7d889e6c79debc5428cc7c7fa98aeced63e5f2be Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 19 Mar 2026 20:02:14 +0100 Subject: [PATCH 03/10] fix: exclude node_modules from Docusaurus docs path --- docs/docusaurus.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index e4bcd38..020a401 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -27,6 +27,7 @@ const config = { ({ docs: { path: './', + exclude: ['**/node_modules/**'], sidebarPath: require.resolve('./sidebars.js'), editUrl: 'https://github.com/ConductionNL/nldesign/tree/main/docs/', From 7f122b64c86a5a26bec841d72e027bbd76fea2ef Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 19 Mar 2026 22:34:57 +0100 Subject: [PATCH 04/10] feat: add Dutch (nl) locale support for documentation --- docs/docusaurus.config.js | 10 +- docs/i18n/nl/code.json | 329 ++++++++++++++++++ .../current.json | 18 + .../nl/docusaurus-theme-classic/footer.json | 22 ++ .../nl/docusaurus-theme-classic/navbar.json | 18 + 5 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 docs/i18n/nl/code.json create mode 100644 docs/i18n/nl/docusaurus-plugin-content-docs/current.json create mode 100644 docs/i18n/nl/docusaurus-theme-classic/footer.json create mode 100644 docs/i18n/nl/docusaurus-theme-classic/navbar.json diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 020a401..08df751 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -17,7 +17,11 @@ const config = { i18n: { defaultLocale: 'en', - locales: ['en'], + locales: ['en', 'nl'], + localeConfigs: { + en: { label: 'English' }, + nl: { label: 'Nederlands' }, + }, }, presets: [ @@ -61,6 +65,10 @@ const config = { label: 'GitHub', position: 'right', }, + { + type: 'localeDropdown', + position: 'right', + }, ], }, footer: { diff --git a/docs/i18n/nl/code.json b/docs/i18n/nl/code.json new file mode 100644 index 0000000..5aaed38 --- /dev/null +++ b/docs/i18n/nl/code.json @@ -0,0 +1,329 @@ +{ + "theme.ErrorPageContent.title": { + "message": "Deze pagina is gecrasht.", + "description": "The title of the fallback page when the page crashed" + }, + "theme.BackToTopButton.buttonAriaLabel": { + "message": "Scroll naar boven", + "description": "The ARIA label for the back to top button" + }, + "theme.blog.archive.title": { + "message": "Archief", + "description": "The page & hero title of the blog archive page" + }, + "theme.blog.archive.description": { + "message": "Archief", + "description": "The page & hero description of the blog archive page" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "Paginanavigatie blog", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "Nieuwere items", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "Oudere items", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "Paginanavigatie blog", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "Nieuwer bericht", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "Ouder bericht", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.tags.tagsPageLink": { + "message": "Laat alle tags zien", + "description": "The label of the link targeting the tag list page" + }, + "theme.colorToggle.ariaLabel.mode.system": { + "message": "system mode", + "description": "The name for the system color mode" + }, + "theme.colorToggle.ariaLabel.mode.light": { + "message": "lichte modus", + "description": "The name for the light color mode" + }, + "theme.colorToggle.ariaLabel.mode.dark": { + "message": "donkere modus", + "description": "The name for the dark color mode" + }, + "theme.colorToggle.ariaLabel": { + "message": "Schakel tussen donkere en lichte modus (momenteel {mode})", + "description": "The ARIA label for the color mode toggle" + }, + "theme.docs.breadcrumbs.navAriaLabel": { + "message": "Broodkruimels", + "description": "The ARIA label for the breadcrumbs" + }, + "theme.docs.DocCard.categoryDescription.plurals": { + "message": "1 artikel|{count} artikelen", + "description": "The default description for a category card in the generated index about how many items this category includes" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "Documentatie pagina", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "Vorige", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "Volgende", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.tagDocListPageTitle.nDocsTagged": { + "message": "Een artikel getagd|{count} artikelen getagd", + "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.tagDocListPageTitle": { + "message": "{nDocsTagged} met \"{tagName}\"", + "description": "The title of the page for a docs tag" + }, + "theme.docs.versionBadge.label": { + "message": "Versie: {versionLabel}" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "Dit is nog niet uitgegeven documentatie voor {siteTitle}, versie {versionLabel}", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "Dit is de documentatie voor {siteTitle} {versionLabel}, welke niet langer actief wordt onderhouden.", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "Voor de huidige documentatie, zie de {latestVersionLink} ({versionLabel}).", + "description": "The label used to tell the user to check the latest version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "laatste versie", + "description": "The label used for the latest version suggestion link label" + }, + "theme.common.editThisPage": { + "message": "Bewerk deze pagina", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "Direct link naar {heading}", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": " op {date}", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": " door {user}", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "Laatst bijgewerkt{atDate}{byUser}", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.navbar.mobileVersionsDropdown.label": { + "message": "Versies", + "description": "The label for the navbar versions dropdown on mobile view" + }, + "theme.NotFound.title": { + "message": "Pagina niet gevonden", + "description": "The title of the 404 page" + }, + "theme.tags.tagsListLabel": { + "message": "Tags:", + "description": "The label alongside a tag list" + }, + "theme.admonition.caution": { + "message": "pas op", + "description": "The default label used for the Caution admonition (:::caution)" + }, + "theme.admonition.danger": { + "message": "gevaar", + "description": "The default label used for the Danger admonition (:::danger)" + }, + "theme.admonition.info": { + "message": "info", + "description": "The default label used for the Info admonition (:::info)" + }, + "theme.admonition.note": { + "message": "notitie", + "description": "The default label used for the Note admonition (:::note)" + }, + "theme.admonition.tip": { + "message": "tip", + "description": "The default label used for the Tip admonition (:::tip)" + }, + "theme.admonition.warning": { + "message": "waarschuwing", + "description": "The default label used for the Warning admonition (:::warning)" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "Sluiten", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.sidebar.navAriaLabel": { + "message": "Navigatie recente blogitems", + "description": "The ARIA label for recent posts in the blog sidebar" + }, + "theme.DocSidebarItem.expandCategoryAriaLabel": { + "message": "Categorie zijbalk uitklappen '{label}'", + "description": "The ARIA label to expand the sidebar category" + }, + "theme.DocSidebarItem.collapseCategoryAriaLabel": { + "message": "Categorie zijbalk inklappen '{label}'", + "description": "The ARIA label to collapse the sidebar category" + }, + "theme.IconExternalLink.ariaLabel": { + "message": "(opens in new tab)", + "description": "The ARIA label for the external link icon" + }, + "theme.NavBar.navAriaLabel": { + "message": "Main", + "description": "The ARIA label for the main navigation" + }, + "theme.navbar.mobileLanguageDropdown.label": { + "message": "Talen", + "description": "The label for the mobile language switcher dropdown" + }, + "theme.NotFound.p1": { + "message": "We kunnen niet vinden waar je naar op zoek bent.", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "Neem contact op met de eigenaar van de website die naar de originele URL heeft geleid en laat weten dat de link niet meer werkt.", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.TOCCollapsible.toggleButtonLabel": { + "message": "Op deze pagina", + "description": "The label used by the button on the collapsible TOC component" + }, + "theme.blog.post.readMore": { + "message": "Lees meer", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.readMoreLabel": { + "message": "Lees meer over {title}", + "description": "The ARIA label for the link to full blog posts from excerpts" + }, + "theme.blog.post.readingTime.plurals": { + "message": "Een minuut leestijd|{readingTime} minuten leestijd", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.CodeBlock.copy": { + "message": "Kopieer", + "description": "The copy button label on code blocks" + }, + "theme.CodeBlock.copied": { + "message": "Gekopieerd", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "Kopieer code naar klembord", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.wordWrapToggle": { + "message": "Tekstterugloop in-/uitschakelen", + "description": "The title attribute for toggle word wrapping button of code block lines" + }, + "theme.docs.breadcrumbs.home": { + "message": "Homepagina", + "description": "The ARIA label for the home page in the breadcrumbs" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "Zijbalk inklappen", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "Zijbalk inklappen", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.navAriaLabel": { + "message": "Docs zijbalk", + "description": "The ARIA label for the sidebar navigation" + }, + "theme.docs.sidebar.closeSidebarButtonAriaLabel": { + "message": "Sluit navigatiebalk", + "description": "The ARIA label for close button of mobile sidebar" + }, + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { + "message": "← Terug naar het hoofdmenu", + "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" + }, + "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { + "message": "Navigatiebalk schakelen", + "description": "The ARIA label for hamburger menu button of mobile navigation" + }, + "theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": { + "message": "Expand the dropdown", + "description": "The ARIA label of the button to expand the mobile dropdown navbar item" + }, + "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": { + "message": "Collapse the dropdown", + "description": "The ARIA label of the button to collapse the mobile dropdown navbar item" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "Zijbalk uitklappen", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "Zijbalk uitklappen", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.blog.post.plurals": { + "message": "Een bericht|{count} berichten", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "{nPosts} getagd met \"{tagName}\"", + "description": "The title of the page for a blog tag" + }, + "theme.blog.author.pageTitle": { + "message": "{authorName} - {nPosts}", + "description": "The title of the page for a blog author" + }, + "theme.blog.authorsList.pageTitle": { + "message": "Auteurs", + "description": "The title of the authors page" + }, + "theme.blog.authorsList.viewAll": { + "message": "Bekijk alle auteurs", + "description": "The label of the link targeting the blog authors page" + }, + "theme.blog.author.noPosts": { + "message": "Deze auteur heeft nog geen berichten geschreven.", + "description": "The text for authors with 0 blog post" + }, + "theme.contentVisibility.unlistedBanner.title": { + "message": "Verborgen page", + "description": "The unlisted content banner title" + }, + "theme.contentVisibility.unlistedBanner.message": { + "message": "Deze pagina is verborgen. Zoekmachines indexeren deze niet en alleen gebruikers met een directe link kunnen deze openen.", + "description": "The unlisted content banner message" + }, + "theme.contentVisibility.draftBanner.title": { + "message": "Concept pagina", + "description": "The draft content banner title" + }, + "theme.contentVisibility.draftBanner.message": { + "message": "Deze pagina is een concept. Deze zal alleen zichtbaar zijn in de ontwikkelomgeving en uitgesloten worden van de productie build.", + "description": "The draft content banner message" + }, + "theme.ErrorPageContent.tryAgain": { + "message": "Probeer opnieuw", + "description": "The label of the button to try again rendering when the React error boundary captures an error" + }, + "theme.common.skipToMainContent": { + "message": "Ga naar hoofdinhoud", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + }, + "theme.tags.tagsPageTitle": { + "message": "Tags", + "description": "The title of the tag list page" + } +} diff --git a/docs/i18n/nl/docusaurus-plugin-content-docs/current.json b/docs/i18n/nl/docusaurus-plugin-content-docs/current.json new file mode 100644 index 0000000..2533eba --- /dev/null +++ b/docs/i18n/nl/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,18 @@ +{ + "version.label": { + "message": "Volgende", + "description": "The label for version current" + }, + "sidebar.tutorialSidebar.category.Getting Started": { + "message": "Aan de slag", + "description": "The label for category 'Getting Started' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Features": { + "message": "Functionaliteiten", + "description": "The label for category 'Features' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Reference": { + "message": "Referentie", + "description": "The label for category 'Reference' in sidebar 'tutorialSidebar'" + } +} diff --git a/docs/i18n/nl/docusaurus-theme-classic/footer.json b/docs/i18n/nl/docusaurus-theme-classic/footer.json new file mode 100644 index 0000000..16db0a4 --- /dev/null +++ b/docs/i18n/nl/docusaurus-theme-classic/footer.json @@ -0,0 +1,22 @@ +{ + "link.title.Docs": { + "message": "Documentatie", + "description": "The title of the footer links column with title=Docs in the footer" + }, + "link.title.Community": { + "message": "Community", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.item.label.Documentation": { + "message": "Documentatie", + "description": "The label of footer link with label=Documentation linking to /docs" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/ConductionNL/nldesign" + }, + "copyright": { + "message": "Copyright © 2026 for Open Webconcept by Conduction B.V.", + "description": "The footer copyright" + } +} diff --git a/docs/i18n/nl/docusaurus-theme-classic/navbar.json b/docs/i18n/nl/docusaurus-theme-classic/navbar.json new file mode 100644 index 0000000..6f6de27 --- /dev/null +++ b/docs/i18n/nl/docusaurus-theme-classic/navbar.json @@ -0,0 +1,18 @@ +{ + "title": { + "message": "NL Design", + "description": "The title in the navbar" + }, + "logo.alt": { + "message": "NL Design Logo", + "description": "The alt text of navbar logo" + }, + "item.label.Documentation": { + "message": "Documentatie", + "description": "Navbar item with label Documentation" + }, + "item.label.GitHub": { + "message": "GitHub", + "description": "Navbar item with label GitHub" + } +} From 39376235ea0e61c8983e9ef4972b88f1dcf89c93 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 20 Mar 2026 09:11:36 +0100 Subject: [PATCH 05/10] feat: Enrich all 7 NLDesign theming specs with deep research --- openspec/specs/admin-settings/spec.md | 272 +++++++++++++---- openspec/specs/css-architecture/spec.md | 264 ++++++++++++---- openspec/specs/hide-slogan/spec.md | 169 +++++++++-- openspec/specs/menu-labels/spec.md | 209 ++++++++++--- openspec/specs/prometheus-metrics/spec.md | 350 +++++++++++++++++++--- openspec/specs/theming-sync/spec.md | 242 ++++++++++++--- openspec/specs/token-sets/spec.md | 229 +++++++++++--- 7 files changed, 1423 insertions(+), 312 deletions(-) diff --git a/openspec/specs/admin-settings/spec.md b/openspec/specs/admin-settings/spec.md index a3f0a20..fe21470 100644 --- a/openspec/specs/admin-settings/spec.md +++ b/openspec/specs/admin-settings/spec.md @@ -1,17 +1,18 @@ --- -status: reviewed +status: enriched reviewed_date: 2026-02-28 +enriched_date: 2026-03-20 --- # Admin Settings Specification ## Purpose -Defines the admin settings panel for the NL Design app. The settings panel is located in Nextcloud's administration area under the Theming section. It provides controls for selecting the active token set, toggling the hide slogan feature, toggling show menu labels, and previewing the selected theme. The UI is built with vanilla PHP templates and vanilla JavaScript (no Vue or webpack). +Defines the admin settings panel for the NL Design app. The settings panel is located in Nextcloud's administration area under the Theming section. It provides controls for selecting the active token set, toggling the hide slogan feature, toggling show menu labels, and previewing the selected theme. The UI is built with vanilla PHP templates and vanilla JavaScript (no Vue or webpack). Additionally, the panel hosts the token editor for customizing individual Nextcloud CSS tokens, and triggers the theming sync dialog when a token set with theming metadata is selected. ## Requirements ### REQ-ASET-001: Settings Panel Registration -The admin settings panel MUST be registered in the Nextcloud Theming section. +The admin settings panel MUST be registered in the Nextcloud Theming section with a defined priority. #### Scenario: Settings panel appears in admin area - GIVEN the nldesign app is enabled @@ -20,37 +21,85 @@ The admin settings panel MUST be registered in the Nextcloud Theming section. - AND it MUST have priority 50 (via `Admin::getPriority()`) - AND it MUST be in the `theming` section (via `Admin::getSection()`) -#### Scenario: Settings panel loads template +#### Scenario: Settings panel position relative to Nextcloud theming +- GIVEN Nextcloud's built-in theming settings have default priority +- WHEN the admin views the Theming settings page +- THEN the NL Design section MUST appear below the default Nextcloud theming section +- AND both sections MUST be independently scrollable + +#### Scenario: Settings panel is absent when app is disabled +- GIVEN the nldesign app is not enabled +- WHEN the admin navigates to Settings -> Administration -> Theming +- THEN the "NL Design System Theme" section MUST NOT appear +- AND no nldesign CSS MUST be injected into the page + +### REQ-ASET-002: Template Response and Parameters +The settings form MUST return a `TemplateResponse` with all required parameters for the admin template. + +#### Scenario: Settings panel loads template with all parameters - GIVEN the admin opens the NL Design settings panel - WHEN `Admin::getForm()` is called - THEN it MUST return a `TemplateResponse` for `settings/admin` -- AND the template parameters MUST include `tokenSets` (array of all available token sets) -- AND the template parameters MUST include `currentTokenSet` (string, current active token set id) -- AND the template parameters MUST include `hideSlogan` (boolean) -- AND the template parameters MUST include `showMenuLabels` (boolean) +- AND the template parameters MUST include `tokenSets` (array of all available token sets from `TokenSetService::getAvailableTokenSets()`) +- AND the template parameters MUST include `currentTokenSet` (string, current active token set id, default `'nextcloud'`) +- AND the template parameters MUST include `hideSlogan` (boolean, from IConfig `hide_slogan` compared with `=== '1'`) +- AND the template parameters MUST include `showMenuLabels` (boolean, from IConfig `show_menu_labels` compared with `=== '1'`) + +#### Scenario: Token sets include design system metadata +- GIVEN `Admin::getForm()` retrieves token sets +- WHEN the `tokenSets` parameter is populated +- THEN each token set object MUST have `id`, `name`, `description`, and `design_system` fields +- AND token sets with theming metadata MUST include the `theming` object + +#### Scenario: Default values for fresh installation +- GIVEN nldesign is freshly installed with no configuration +- WHEN `Admin::getForm()` is called +- THEN `currentTokenSet` MUST be `'nextcloud'` +- AND `hideSlogan` MUST be `false` +- AND `showMenuLabels` MUST be `false` -### REQ-ASET-002: Token Set Selector -The settings panel MUST provide a dropdown for selecting the active design token set. +### REQ-ASET-003: Token Set Selector Dropdown +The settings panel MUST provide a searchable dropdown for selecting the active design token set from all available sets. #### Scenario: Dropdown populated with token sets - GIVEN the settings panel is loaded - AND there are multiple token sets available (discovered from `css/tokens/` directory) - WHEN the dropdown renders -- THEN it MUST contain an `