@cat-factory/app 0.167.1 → 0.168.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +45 -2
  2. package/app/components/auth/UserMenu.vue +9 -2
  3. package/app/components/board/AddTaskModal.vue +28 -9
  4. package/app/components/layout/BoardSwitcher.vue +21 -4
  5. package/app/components/layout/CommandBar.vue +3 -1
  6. package/app/components/layout/LanguageSwitcher.vue +9 -2
  7. package/app/components/layout/SideBar.vue +68 -15
  8. package/app/components/layout/UiModeSwitcher.vue +90 -0
  9. package/app/components/panels/inspector/TaskRunSettings.vue +35 -8
  10. package/app/components/tasks/BugHuntModal.vue +3 -1
  11. package/app/components/tasks/ContextIssuePicker.vue +22 -5
  12. package/app/components/tasks/TaskImportModal.vue +1 -1
  13. package/app/composables/api/errors.ts +16 -0
  14. package/app/composables/api/tasks.ts +5 -2
  15. package/app/composables/useNavContributions.ts +3 -0
  16. package/app/docs/consumer-extensions.md +6 -1
  17. package/app/modular/nav-contributions.spec.ts +59 -1
  18. package/app/modular/nav-contributions.ts +62 -4
  19. package/app/modular/nav-gates.ts +7 -1
  20. package/app/modular/registry.spec.ts +1 -0
  21. package/app/stores/bugHunt.ts +2 -10
  22. package/app/stores/tasks.ts +7 -4
  23. package/app/stores/uiMode.spec.ts +151 -0
  24. package/app/stores/uiMode.ts +88 -0
  25. package/app/utils/uiMode.spec.ts +67 -0
  26. package/app/utils/uiMode.ts +71 -0
  27. package/i18n/locales/de.json +17 -4
  28. package/i18n/locales/en.json +20 -4
  29. package/i18n/locales/es.json +17 -4
  30. package/i18n/locales/fr.json +17 -4
  31. package/i18n/locales/he.json +17 -4
  32. package/i18n/locales/it.json +17 -4
  33. package/i18n/locales/ja.json +17 -4
  34. package/i18n/locales/pl.json +17 -4
  35. package/i18n/locales/tr.json +17 -4
  36. package/i18n/locales/uk.json +17 -4
  37. package/nuxt.config.ts +5 -0
  38. package/package.json +2 -2
@@ -0,0 +1,151 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { createPinia, setActivePinia } from 'pinia'
3
+ import { nextTick } from 'vue'
4
+ import { useUiModeStore } from '~/stores/uiMode'
5
+ import type { UiMode } from '~/utils/uiMode'
6
+
7
+ /**
8
+ * Point `runtimeConfig.public.uiMode` at a value and hand back a store built against it.
9
+ * The env value is read ONCE at store setup (it is build-time-baked in a `ssr: false` SPA),
10
+ * so every case needs a fresh Pinia — which is also what proves the read isn't reactive.
11
+ */
12
+ function storeWithEnv(uiMode: string | undefined) {
13
+ vi.stubGlobal('useRuntimeConfig', () => ({ public: { apiBase: '', uiMode } }))
14
+ setActivePinia(createPinia())
15
+ return useUiModeStore()
16
+ }
17
+
18
+ describe('useUiModeStore mode resolution', () => {
19
+ beforeEach(() => {
20
+ // The default runtime-config stub carries no `uiMode` at all — the shape a deployment
21
+ // that never set NUXT_PUBLIC_UI_MODE ends up with once Nuxt applies the '' default.
22
+ vi.stubGlobal('useRuntimeConfig', () => ({ public: { apiBase: '', uiMode: '' } }))
23
+ })
24
+
25
+ it('boots in basic mode with no env pin and nothing stored', () => {
26
+ const ui = useUiModeStore()
27
+ expect(ui.mode).toBe('basic')
28
+ expect(ui.isAdvanced).toBe(false)
29
+ expect(ui.envPinned).toBe(false)
30
+ })
31
+
32
+ it('honours the browser-stored choice when no env pin is present', () => {
33
+ const ui = useUiModeStore()
34
+ ui.setMode('advanced')
35
+ expect(ui.mode).toBe('advanced')
36
+ expect(ui.isAdvanced).toBe(true)
37
+ // Persisted, so a reload restores it (the persist plugin picks `storedMode`).
38
+ expect(ui.storedMode).toBe('advanced')
39
+ })
40
+
41
+ it('lets the env pin override a conflicting stored choice, in both directions', () => {
42
+ const pinnedBasic = storeWithEnv('advanced')
43
+ pinnedBasic.storedMode = 'basic'
44
+ expect(pinnedBasic.mode).toBe('advanced')
45
+
46
+ const pinnedAdvanced = storeWithEnv('basic')
47
+ pinnedAdvanced.storedMode = 'advanced'
48
+ expect(pinnedAdvanced.mode).toBe('basic')
49
+ })
50
+
51
+ it('refuses to record a preference under an env pin', () => {
52
+ const ui = storeWithEnv('basic')
53
+ expect(ui.envPinned).toBe(true)
54
+ ui.setMode('advanced')
55
+ ui.toggleMode()
56
+ // Nothing written: the resolver would ignore it, so persisting it would be a lie.
57
+ expect(ui.storedMode).toBeNull()
58
+ expect(ui.mode).toBe('basic')
59
+ })
60
+
61
+ it('ignores an unrecognised env value rather than failing the boot', () => {
62
+ const ui = storeWithEnv('expert')
63
+ expect(ui.envPinned).toBe(false)
64
+ expect(ui.mode).toBe('basic')
65
+ ui.setMode('advanced')
66
+ expect(ui.mode).toBe('advanced')
67
+ })
68
+
69
+ it('toggleMode flips between the two tiers', () => {
70
+ const ui = useUiModeStore()
71
+ ui.toggleMode()
72
+ expect(ui.mode).toBe('advanced')
73
+ ui.toggleMode()
74
+ expect(ui.mode).toBe('basic')
75
+ })
76
+ })
77
+
78
+ describe('useUiModeStore navbar collapse', () => {
79
+ beforeEach(() => {
80
+ vi.stubGlobal('useRuntimeConfig', () => ({ public: { apiBase: '', uiMode: '' } }))
81
+ })
82
+
83
+ it('starts collapsed in basic mode and expanded in advanced mode', () => {
84
+ expect(useUiModeStore().navCollapsed).toBe(true)
85
+
86
+ const advanced = storeWithEnv('advanced')
87
+ expect(advanced.navCollapsed).toBe(false)
88
+ })
89
+
90
+ it('remembers the rail choice in EITHER tier, across a reload', () => {
91
+ // The persisted shape is per-tier, so an expand in basic is remembered just as an advanced
92
+ // one is — the default is what differs between the tiers, not whether a choice sticks.
93
+ const ui = useUiModeStore()
94
+ ui.toggleNav()
95
+ expect(ui.navCollapsed).toBe(false)
96
+ expect(ui.railCollapsed.basic).toBe(false)
97
+
98
+ const advanced = storeWithEnv('advanced')
99
+ advanced.toggleNav()
100
+ expect(advanced.navCollapsed).toBe(true)
101
+ expect(advanced.railCollapsed.advanced).toBe(true)
102
+ })
103
+
104
+ it('keeps each tier’s rail state independent across a switch', async () => {
105
+ const ui = useUiModeStore()
106
+ // Expand in basic, then collapse in advanced: neither choice may leak into the other tier.
107
+ ui.toggleNav()
108
+ expect(ui.navCollapsed).toBe(false)
109
+
110
+ ui.setMode('advanced')
111
+ await nextTick()
112
+ expect(ui.navCollapsed).toBe(false) // advanced's own default
113
+ ui.toggleNav()
114
+ expect(ui.navCollapsed).toBe(true)
115
+
116
+ ui.setMode('basic')
117
+ await nextTick()
118
+ expect(ui.navCollapsed).toBe(false) // the basic expand survived the round trip
119
+
120
+ ui.setMode('advanced')
121
+ await nextTick()
122
+ expect(ui.navCollapsed).toBe(true)
123
+ })
124
+
125
+ it('falls back to the tier default when the restored preference is missing a key', () => {
126
+ // A persisted blob from an older build (or a hand-edited cookie) can omit a tier; a rail
127
+ // stuck on `undefined` would render expanded, which is wrong for basic.
128
+ const ui = useUiModeStore()
129
+ ui.railCollapsed = {} as Record<UiMode, boolean>
130
+ expect(ui.navCollapsed).toBe(true)
131
+ ui.setMode('advanced')
132
+ expect(ui.navCollapsed).toBe(false)
133
+ })
134
+
135
+ it('setNavCollapsed is idempotent', () => {
136
+ const ui = storeWithEnv('advanced')
137
+ ui.setNavCollapsed(true)
138
+ ui.setNavCollapsed(true)
139
+ expect(ui.navCollapsed).toBe(true)
140
+ ui.setNavCollapsed(false)
141
+ expect(ui.navCollapsed).toBe(false)
142
+ })
143
+ })
144
+
145
+ describe('useUiModeStore typing', () => {
146
+ it('exposes the mode as the closed union', () => {
147
+ const ui = useUiModeStore()
148
+ const mode: UiMode = ui.mode
149
+ expect(['basic', 'advanced']).toContain(mode)
150
+ })
151
+ })
@@ -0,0 +1,88 @@
1
+ import { defineStore } from 'pinia'
2
+ import { computed, ref } from 'vue'
3
+ import { DEFAULT_RAIL_COLLAPSED, parseUiMode, resolveUiMode, type UiMode } from '~/utils/uiMode'
4
+
5
+ /**
6
+ * The interface tier (`basic` / `advanced`) and the side-navbar collapse state.
7
+ *
8
+ * Mode resolution is `env → browser-stored → basic` (see `utils/uiMode.ts`). The env value
9
+ * is `runtimeConfig.public.uiMode`, i.e. `NUXT_PUBLIC_UI_MODE`: the SPA is `ssr: false`, so
10
+ * — exactly like `apiBase` — it is baked in at build time and cannot change while the app
11
+ * is loaded. It is therefore read ONCE here rather than tracked reactively. Only the user's
12
+ * own choice is persisted, so a deployment that later drops its env pin falls back to
13
+ * whatever the user last picked instead of to the default.
14
+ *
15
+ * The collapse state is a PER-TIER preference (`railCollapsed`), not a single boolean. The two
16
+ * tiers want different resting states — basic opens railed, advanced expanded — so one shared
17
+ * flag can only serve one of them: it would either override the other tier's default or throw
18
+ * away the user's choice on every switch. Keying the preference by tier gives each its own
19
+ * default (`DEFAULT_RAIL_COLLAPSED`) AND its own memory, so switching tiers restores what that
20
+ * tier last looked like and neither has to forget. That is also why there is no watcher here:
21
+ * the collapse follows `mode` because it is indexed by it, not because something resets on
22
+ * change.
23
+ */
24
+ export const useUiModeStore = defineStore(
25
+ 'uiMode',
26
+ () => {
27
+ const envMode = parseUiMode(useRuntimeConfig().public.uiMode)
28
+
29
+ /** The user's explicit pick, persisted. `null` until they choose one. */
30
+ const storedMode = ref<UiMode | null>(null)
31
+ /** The rail state each tier was last left in, persisted. Seeded from the per-tier defaults. */
32
+ const railCollapsed = ref<Record<UiMode, boolean>>({ ...DEFAULT_RAIL_COLLAPSED })
33
+
34
+ const mode = computed<UiMode>(() => resolveUiMode(envMode, storedMode.value))
35
+ const isAdvanced = computed(() => mode.value === 'advanced')
36
+ /** Pinned by the deployment: the switcher is read-only, since a write would be ignored. */
37
+ const envPinned = computed(() => envMode !== null)
38
+
39
+ // `?? the default` because the restored value is untrusted input, exactly like the env
40
+ // string: a persisted blob written by an older build (or hand-edited) can be missing this
41
+ // tier's key, and a rail stuck on `undefined` would render as expanded in basic mode.
42
+ const navCollapsed = computed(
43
+ () => railCollapsed.value[mode.value] ?? DEFAULT_RAIL_COLLAPSED[mode.value],
44
+ )
45
+
46
+ /**
47
+ * Record the user's pick. A no-op under an env pin — the resolver would ignore it.
48
+ *
49
+ * This guard is hygiene, not the invariant. `storedMode` is returned from the store because
50
+ * a setup store can only persist what it returns (as in `auth`, `accounts`, `workspace`), so
51
+ * a caller CAN write it directly and skip this check. That is harmless by construction:
52
+ * `mode` runs `resolveUiMode(env, stored)`, which consults the env pin FIRST, so a direct
53
+ * write can leave a stale persisted value but can never change the tier under a pin. The
54
+ * "env pin wins, in both directions" case above is what actually pins that.
55
+ */
56
+ function setMode(next: UiMode) {
57
+ if (envPinned.value) return
58
+ storedMode.value = next
59
+ }
60
+
61
+ function toggleMode() {
62
+ setMode(isAdvanced.value ? 'basic' : 'advanced')
63
+ }
64
+
65
+ /** Remember the rail state for the CURRENT tier; the other tier keeps its own. */
66
+ function setNavCollapsed(collapsed: boolean) {
67
+ railCollapsed.value = { ...railCollapsed.value, [mode.value]: collapsed }
68
+ }
69
+
70
+ function toggleNav() {
71
+ setNavCollapsed(!navCollapsed.value)
72
+ }
73
+
74
+ return {
75
+ mode,
76
+ isAdvanced,
77
+ envPinned,
78
+ storedMode,
79
+ railCollapsed,
80
+ navCollapsed,
81
+ setMode,
82
+ toggleMode,
83
+ setNavCollapsed,
84
+ toggleNav,
85
+ }
86
+ },
87
+ { persist: { pick: ['storedMode', 'railCollapsed'] } },
88
+ )
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { DEFAULT_UI_MODE, parseUiMode, resolveUiMode, showOverrideField, UI_MODES } from './uiMode'
3
+
4
+ describe('parseUiMode', () => {
5
+ it('accepts every known mode, case- and whitespace-insensitively', () => {
6
+ for (const mode of UI_MODES) {
7
+ expect(parseUiMode(mode)).toBe(mode)
8
+ expect(parseUiMode(` ${mode.toUpperCase()} `)).toBe(mode)
9
+ }
10
+ })
11
+
12
+ it('reports no opinion for an unset / unrecognised / non-string value', () => {
13
+ // '' is what `runtimeConfig.public.uiMode` carries when NUXT_PUBLIC_UI_MODE is unset, so
14
+ // it MUST read as "no env pin" rather than as an invalid mode.
15
+ for (const raw of ['', ' ', 'expert', 'Basic mode', undefined, null, 1, {}]) {
16
+ expect(parseUiMode(raw)).toBeNull()
17
+ }
18
+ })
19
+ })
20
+
21
+ describe('resolveUiMode', () => {
22
+ it('lets the env pin win over the stored choice', () => {
23
+ expect(resolveUiMode('basic', 'advanced')).toBe('basic')
24
+ expect(resolveUiMode('advanced', 'basic')).toBe('advanced')
25
+ })
26
+
27
+ it('falls back to the stored choice, then to the default', () => {
28
+ expect(resolveUiMode(null, 'advanced')).toBe('advanced')
29
+ expect(resolveUiMode(null, null)).toBe(DEFAULT_UI_MODE)
30
+ expect(DEFAULT_UI_MODE).toBe('basic')
31
+ })
32
+ })
33
+
34
+ describe('showOverrideField', () => {
35
+ it('shows every override control in advanced mode, set or not', () => {
36
+ expect(showOverrideField(true)).toBe(true)
37
+ expect(showOverrideField(true, null)).toBe(true)
38
+ expect(showOverrideField(true, 'preset_1')).toBe(true)
39
+ })
40
+
41
+ it('hides an unset override in basic mode', () => {
42
+ // The three shapes an unset override arrives in: absent, explicitly null, or the empty
43
+ // string a cleared picker writes. All mean "inherit the default", so nothing is concealed.
44
+ for (const unset of [undefined, null, '']) expect(showOverrideField(false, unset)).toBe(false)
45
+ expect(showOverrideField(false)).toBe(false)
46
+ })
47
+
48
+ it('reveals a SET override in basic mode so it stays visible and clearable', () => {
49
+ // The regression this guards: a block that already carries an override would otherwise run
50
+ // on settings a basic-mode user can neither see nor clear.
51
+ expect(showOverrideField(false, 'policy_strict')).toBe(true)
52
+ expect(showOverrideField(false, true)).toBe(true)
53
+ })
54
+
55
+ it('treats `false` as a real override, not as absence', () => {
56
+ // `technical: false` means "business task" — an explicit choice that must NOT be read as
57
+ // unset, which a plain truthiness check would get wrong.
58
+ expect(showOverrideField(false, false)).toBe(true)
59
+ })
60
+
61
+ it('reveals a multi-value group when any one of its values is set', () => {
62
+ // The tracker-writeback group edits three tri-states behind one heading.
63
+ expect(showOverrideField(false, null, null, null)).toBe(false)
64
+ expect(showOverrideField(false, null, false, null)).toBe(true)
65
+ expect(showOverrideField(false, null, null, true)).toBe(true)
66
+ })
67
+ })
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The interface tier the SPA renders at: `basic` shows the everyday surface, `advanced`
3
+ * shows every destination and every run/pipeline option. Pure resolution logic, kept out
4
+ * of the store so it is testable without Pinia or a Nuxt runtime.
5
+ *
6
+ * Precedence is fixed and NOT negotiable per surface: the deployment's env value always
7
+ * wins over the browser-stored user choice, which wins over the `basic` default. That
8
+ * ordering is what lets an operator pin a fleet of kiosk-ish deployments to one tier
9
+ * without a per-browser reset, so `setMode` is a no-op while the env pin is present
10
+ * rather than writing a preference the resolver would then ignore.
11
+ */
12
+ export const UI_MODES = ['basic', 'advanced'] as const
13
+
14
+ export type UiMode = (typeof UI_MODES)[number]
15
+
16
+ /** The tier a deployment gets when neither env nor the browser says otherwise. */
17
+ export const DEFAULT_UI_MODE: UiMode = 'basic'
18
+
19
+ /**
20
+ * The side-navbar rail state each tier STARTS in, before the user touches the toggle. Basic
21
+ * opens railed (the icon rail is part of what makes it feel basic) and advanced opens
22
+ * expanded. A default, not a rule: the preference is per-tier and remembered, so an explicit
23
+ * choice in either tier survives both a reload and a round trip through the other tier.
24
+ */
25
+ export const DEFAULT_RAIL_COLLAPSED: Record<UiMode, boolean> = { basic: true, advanced: false }
26
+
27
+ /**
28
+ * Coerce an untrusted value (a `NUXT_PUBLIC_UI_MODE` string baked into the bundle, or a
29
+ * restored cookie/localStorage value) to a known mode. Anything unrecognised — including
30
+ * the empty string the runtime-config default carries when the env var is unset — resolves
31
+ * to `null`, i.e. "this layer has no opinion", so the next one down decides. Never throws:
32
+ * a typo'd env value must degrade to the default rather than fail the boot.
33
+ */
34
+ export function parseUiMode(raw: unknown): UiMode | null {
35
+ if (typeof raw !== 'string') return null
36
+ const value = raw.trim().toLowerCase()
37
+ return (UI_MODES as readonly string[]).includes(value) ? (value as UiMode) : null
38
+ }
39
+
40
+ /** Apply the precedence: env pin → browser-stored user choice → {@link DEFAULT_UI_MODE}. */
41
+ export function resolveUiMode(env: UiMode | null, stored: UiMode | null): UiMode {
42
+ return env ?? stored ?? DEFAULT_UI_MODE
43
+ }
44
+
45
+ /**
46
+ * An override value is SET when it deviates from the inherited default. `null`/`undefined`
47
+ * (and the empty string a cleared picker writes) mean "inherit"; every other value — INCLUDING
48
+ * `false`, which is a real choice on a tri-state like `technical` — is an explicit override.
49
+ */
50
+ function isOverrideSet(value: unknown): boolean {
51
+ return value != null && value !== ''
52
+ }
53
+
54
+ /**
55
+ * Whether an OVERRIDE control is rendered at the current tier.
56
+ *
57
+ * Basic mode hides the controls whose only job is to override something already decided
58
+ * elsewhere (a workspace-level default, an engine-inferred flag), which is safe ONLY while
59
+ * what remains is exactly the value the hidden control would have shown. The moment a block
60
+ * actually carries an override that stops being true: hiding it would leave a basic-mode user
61
+ * looking at a task that runs on settings they cannot see, let alone clear — and nothing else
62
+ * in the inspector surfaces them. So basic mode hides the control only while EVERY value it
63
+ * edits is unset, and reveals it (exactly as advanced mode renders it, editable, so the
64
+ * override can be cleared) as soon as one is not.
65
+ *
66
+ * Variadic because a single control can own several values — the tracker-writeback group edits
67
+ * three tri-states behind one heading, and any one of them being set must reveal the group.
68
+ */
69
+ export function showOverrideField(isAdvanced: boolean, ...values: readonly unknown[]): boolean {
70
+ return isAdvanced || values.some(isOverrideSet)
71
+ }
@@ -1997,7 +1997,8 @@
1997
1997
  "localModels": "Meine lokalen Runner",
1998
1998
  "sandbox": "Sandbox öffnen",
1999
1999
  "shortcuts": "Tastenkürzel",
2000
- "bugHunt": "Fehlerjagd"
2000
+ "bugHunt": "Fehlerjagd",
2001
+ "toggleUiMode": "Oberflächenmodus wechseln"
2001
2002
  },
2002
2003
  "keywords": {
2003
2004
  "newPipeline": "pipeline agents chain",
@@ -2018,7 +2019,8 @@
2018
2019
  "localModels": "local model runner ollama lm studio llamacpp vllm endpoint",
2019
2020
  "sandbox": "sandbox prompt model test experiment judge fixture benchmark evaluate",
2020
2021
  "shortcuts": "keyboard shortcuts keys hotkeys cheatsheet help",
2021
- "bugHunt": "fehler bug jagd triage backlog tracker nicht zugewiesen"
2022
+ "bugHunt": "fehler bug jagd triage backlog tracker nicht zugewiesen",
2023
+ "toggleUiMode": "oberfläche modus einfach erweitert anzeigen ausblenden"
2022
2024
  }
2023
2025
  },
2024
2026
  "shortcuts": {
@@ -3247,10 +3249,11 @@
3247
3249
  "searchPlaceholder": "Issues durchsuchen oder eine URL/einen Schlüssel einfügen…",
3248
3250
  "refPlaceholder": "Eine Issue-URL oder einen -Schlüssel einfügen…",
3249
3251
  "searchFailed": "Suche fehlgeschlagen: {error}",
3252
+ "searchNeedsRepo": "Dieser Service ist noch mit keinem Repository verknüpft, es gibt also nichts zu durchsuchen. Verknüpfen Sie ihn mit einem Repository oder fügen Sie eine Issue-URL ein, um ein Issue direkt anzuhängen.",
3250
3253
  "imported": "importiert",
3251
3254
  "attachByReference": "{ref} per Referenz anhängen",
3252
- "noMatches": "Keine passenden Issues.",
3253
- "emptySearchable": "Nach Titel suchen oder ein importiertes Issue auswählen.",
3255
+ "noMatches": "Keine passenden Issues im Repository dieses Service. Fügen Sie eine Issue-URL ein, um eines von anderswo anzuhängen.",
3256
+ "emptySearchable": "Durchsuchen Sie das Repository dieses Service nach Titel oder wählen Sie ein importiertes Issue aus.",
3254
3257
  "emptyRefOnly": "Fügen Sie eine Issue-URL oder einen -Schlüssel ein, um es anzuhängen.",
3255
3258
  "sourceLabel": "Quelle",
3256
3259
  "noSource": "Quelle wählen",
@@ -4207,6 +4210,14 @@
4207
4210
  "dismiss": "Schließen"
4208
4211
  }
4209
4212
  },
4213
+ "uiMode": {
4214
+ "switcher": "Oberfläche",
4215
+ "basic": "Einfach",
4216
+ "advanced": "Erweitert",
4217
+ "basicHint": "Nur die alltäglichen Werkzeuge. Für alles auf „Erweitert“ umschalten.",
4218
+ "advancedHint": "Alle Bereiche und alle Ausführungsoptionen.",
4219
+ "pinned": "Von dieser Installation festgelegt"
4220
+ },
4210
4221
  "common": {
4211
4222
  "loading": "Wird geladen…",
4212
4223
  "save": "Speichern",
@@ -4279,6 +4290,8 @@
4279
4290
  "menu": "Navigationsmenü",
4280
4291
  "openMenu": "Menü öffnen",
4281
4292
  "closeMenu": "Menü schließen",
4293
+ "expandSidebar": "Seitenleiste ausklappen",
4294
+ "collapseSidebar": "Seitenleiste einklappen",
4282
4295
  "commandBar": "Suchen oder einen Befehl ausführen…",
4283
4296
  "create": "Erstellen",
4284
4297
  "buildPipeline": "Eine Pipeline erstellen",
@@ -23,6 +23,17 @@
23
23
  "dismiss": "Dismiss"
24
24
  }
25
25
  },
26
+ "uiMode": {
27
+ "switcher": "Interface",
28
+ "@switcher": {
29
+ "description": "Label above the picker that chooses how much of the app is shown (Basic or Advanced). It names the app's user interface, NOT a programming interface."
30
+ },
31
+ "basic": "Basic",
32
+ "advanced": "Advanced",
33
+ "basicHint": "Everyday tools only. Switch to Advanced to see everything.",
34
+ "advancedHint": "Every destination and every run option.",
35
+ "pinned": "Set by this deployment"
36
+ },
26
37
  "common": {
27
38
  "loading": "Loading…",
28
39
  "save": "Save",
@@ -107,6 +118,8 @@
107
118
  "menu": "Navigation menu",
108
119
  "openMenu": "Open menu",
109
120
  "closeMenu": "Close menu",
121
+ "expandSidebar": "Expand sidebar",
122
+ "collapseSidebar": "Collapse sidebar",
110
123
  "commandBar": "Search or run a command…",
111
124
  "create": "Create",
112
125
  "buildPipeline": "Build a pipeline",
@@ -2006,7 +2019,8 @@
2006
2019
  "localModels": "My local runners",
2007
2020
  "sandbox": "Open Sandbox",
2008
2021
  "shortcuts": "Keyboard shortcuts",
2009
- "bugHunt": "Bug hunt"
2022
+ "bugHunt": "Bug hunt",
2023
+ "toggleUiMode": "Switch interface mode"
2010
2024
  },
2011
2025
  "keywords": {
2012
2026
  "newPipeline": "pipeline agents chain",
@@ -2027,7 +2041,8 @@
2027
2041
  "localModels": "local model runner ollama lm studio llamacpp vllm endpoint",
2028
2042
  "sandbox": "sandbox prompt model test experiment judge fixture benchmark evaluate",
2029
2043
  "shortcuts": "keyboard shortcuts keys hotkeys cheatsheet help",
2030
- "bugHunt": "bug hunt triage backlog issue tracker unassigned"
2044
+ "bugHunt": "bug hunt triage backlog issue tracker unassigned",
2045
+ "toggleUiMode": "interface mode basic advanced simple expert show hide"
2031
2046
  }
2032
2047
  },
2033
2048
  "shortcuts": {
@@ -3646,10 +3661,11 @@
3646
3661
  "searchPlaceholder": "Search issues or paste a URL/key…",
3647
3662
  "refPlaceholder": "Paste an issue URL or key…",
3648
3663
  "searchFailed": "Search failed: {error}",
3664
+ "searchNeedsRepo": "This service isn't linked to a repository yet, so there's nothing to search. Link it to a repo, or paste an issue URL to attach one directly.",
3649
3665
  "imported": "imported",
3650
3666
  "attachByReference": "Attach {ref} by reference",
3651
- "noMatches": "No matching issues.",
3652
- "emptySearchable": "Search by title, or pick an imported issue.",
3667
+ "noMatches": "No matching issues in this service's repository. Paste an issue URL to attach one from elsewhere.",
3668
+ "emptySearchable": "Search this service's repository by title, or pick an imported issue.",
3653
3669
  "emptyRefOnly": "Paste an issue URL or key to attach it.",
3654
3670
  "sourceLabel": "Source",
3655
3671
  "noSource": "Choose a source",
@@ -23,6 +23,14 @@
23
23
  "dismiss": "Descartar"
24
24
  }
25
25
  },
26
+ "uiMode": {
27
+ "switcher": "Interfaz",
28
+ "basic": "Básica",
29
+ "advanced": "Avanzada",
30
+ "basicHint": "Solo las herramientas del día a día. Cambia a Avanzada para verlo todo.",
31
+ "advancedHint": "Todas las secciones y todas las opciones de ejecución.",
32
+ "pinned": "Definido por este despliegue"
33
+ },
26
34
  "common": {
27
35
  "loading": "Cargando…",
28
36
  "save": "Guardar",
@@ -95,6 +103,8 @@
95
103
  "menu": "Menú de navegación",
96
104
  "openMenu": "Abrir menú",
97
105
  "closeMenu": "Cerrar menú",
106
+ "expandSidebar": "Expandir barra lateral",
107
+ "collapseSidebar": "Contraer barra lateral",
98
108
  "commandBar": "Buscar o ejecutar un comando…",
99
109
  "create": "Crear",
100
110
  "buildPipeline": "Crear una canalización",
@@ -1940,7 +1950,8 @@
1940
1950
  "localModels": "Mis ejecutores locales",
1941
1951
  "sandbox": "Abrir el entorno de pruebas",
1942
1952
  "shortcuts": "Atajos de teclado",
1943
- "bugHunt": "Caza de errores"
1953
+ "bugHunt": "Caza de errores",
1954
+ "toggleUiMode": "Cambiar el modo de interfaz"
1944
1955
  },
1945
1956
  "keywords": {
1946
1957
  "newPipeline": "canalización agentes cadena pipeline",
@@ -1961,7 +1972,8 @@
1961
1972
  "localModels": "modelo local ejecutor ollama lm studio llamacpp vllm endpoint",
1962
1973
  "sandbox": "entorno de pruebas prompt modelo prueba experimento juez fixture benchmark evaluar",
1963
1974
  "shortcuts": "atajos teclado teclas ayuda",
1964
- "bugHunt": "error bug caza triaje backlog incidencias sin asignar"
1975
+ "bugHunt": "error bug caza triaje backlog incidencias sin asignar",
1976
+ "toggleUiMode": "interfaz modo básico avanzado mostrar ocultar"
1965
1977
  }
1966
1978
  },
1967
1979
  "integrationsHub": {
@@ -3541,10 +3553,11 @@
3541
3553
  "searchPlaceholder": "Busca incidencias o pega una URL/clave…",
3542
3554
  "refPlaceholder": "Pega la URL o clave de una incidencia…",
3543
3555
  "searchFailed": "La búsqueda falló: {error}",
3556
+ "searchNeedsRepo": "Este servicio aún no está vinculado a un repositorio, así que no hay nada donde buscar. Vincúlalo a un repositorio o pega la URL de una incidencia para adjuntarla directamente.",
3544
3557
  "imported": "importada",
3545
3558
  "attachByReference": "Adjuntar {ref} por referencia",
3546
- "noMatches": "No hay incidencias coincidentes.",
3547
- "emptySearchable": "Busca por título o elige una incidencia importada.",
3559
+ "noMatches": "No hay incidencias coincidentes en el repositorio de este servicio. Pega la URL de una incidencia para adjuntar una de otro sitio.",
3560
+ "emptySearchable": "Busca por título en el repositorio de este servicio o elige una incidencia importada.",
3548
3561
  "emptyRefOnly": "Pega la URL o clave de una incidencia para adjuntarla.",
3549
3562
  "sourceLabel": "Fuente",
3550
3563
  "noSource": "Elige una fuente",
@@ -23,6 +23,14 @@
23
23
  "dismiss": "Fermer"
24
24
  }
25
25
  },
26
+ "uiMode": {
27
+ "switcher": "Interface",
28
+ "basic": "Basique",
29
+ "advanced": "Avancée",
30
+ "basicHint": "Uniquement les outils du quotidien. Passez en mode avancé pour tout afficher.",
31
+ "advancedHint": "Toutes les sections et toutes les options d’exécution.",
32
+ "pinned": "Défini par ce déploiement"
33
+ },
26
34
  "common": {
27
35
  "loading": "Chargement…",
28
36
  "save": "Enregistrer",
@@ -95,6 +103,8 @@
95
103
  "menu": "Menu de navigation",
96
104
  "openMenu": "Ouvrir le menu",
97
105
  "closeMenu": "Fermer le menu",
106
+ "expandSidebar": "Développer la barre latérale",
107
+ "collapseSidebar": "Réduire la barre latérale",
98
108
  "commandBar": "Rechercher ou exécuter une commande…",
99
109
  "create": "Créer",
100
110
  "buildPipeline": "Créer un pipeline",
@@ -1940,7 +1950,8 @@
1940
1950
  "localModels": "Mes exécuteurs locaux",
1941
1951
  "sandbox": "Ouvrir le bac à sable",
1942
1952
  "shortcuts": "Raccourcis clavier",
1943
- "bugHunt": "Chasse aux bugs"
1953
+ "bugHunt": "Chasse aux bugs",
1954
+ "toggleUiMode": "Changer le mode d'interface"
1944
1955
  },
1945
1956
  "keywords": {
1946
1957
  "newPipeline": "pipeline agents chaîne",
@@ -1961,7 +1972,8 @@
1961
1972
  "localModels": "modèle local exécuteur ollama lm studio llamacpp vllm endpoint",
1962
1973
  "sandbox": "bac à sable prompt modèle test expérience juge fixture benchmark évaluer",
1963
1974
  "shortcuts": "raccourcis clavier touches aide",
1964
- "bugHunt": "bug chasse tri backlog tickets non assignés"
1975
+ "bugHunt": "bug chasse tri backlog tickets non assignés",
1976
+ "toggleUiMode": "interface mode simple avancé afficher masquer"
1965
1977
  }
1966
1978
  },
1967
1979
  "integrationsHub": {
@@ -3541,10 +3553,11 @@
3541
3553
  "searchPlaceholder": "Rechercher des tickets ou coller une URL/clé…",
3542
3554
  "refPlaceholder": "Coller l'URL ou la clé d'un ticket…",
3543
3555
  "searchFailed": "Échec de la recherche : {error}",
3556
+ "searchNeedsRepo": "Ce service n'est encore lié à aucun dépôt, il n'y a donc rien à rechercher. Liez-le à un dépôt ou collez l'URL d'un ticket pour en joindre un directement.",
3544
3557
  "imported": "importé",
3545
3558
  "attachByReference": "Joindre {ref} par référence",
3546
- "noMatches": "Aucun ticket correspondant.",
3547
- "emptySearchable": "Recherchez par titre ou choisissez un ticket importé.",
3559
+ "noMatches": "Aucun ticket correspondant dans le dépôt de ce service. Collez l'URL d'un ticket pour en joindre un provenant d'ailleurs.",
3560
+ "emptySearchable": "Recherchez par titre dans le dépôt de ce service ou choisissez un ticket importé.",
3548
3561
  "emptyRefOnly": "Collez l'URL ou la clé d'un ticket pour le joindre.",
3549
3562
  "sourceLabel": "Source",
3550
3563
  "noSource": "Choisir une source",