@innertia-solutions/innertia-nuxt 0.1.11 → 0.1.13

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 (65) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/app.config.ts +10 -10
  3. package/components/Attention.vue +269 -0
  4. package/components/Auth/LoginForm.vue +1 -1
  5. package/components/Chart/index.vue +193 -0
  6. package/components/Container.vue +116 -0
  7. package/components/CopyButton.vue +98 -0
  8. package/components/Files/Card.vue +215 -0
  9. package/components/Files/Preview.vue +675 -0
  10. package/components/Files/Upload.vue +408 -0
  11. package/components/Files/UploadModal.vue +83 -0
  12. package/components/Forms/InputPin.vue +162 -0
  13. package/components/InfoToolbar.vue +54 -0
  14. package/components/Kanban/Standard.vue +588 -0
  15. package/components/Loader.vue +358 -0
  16. package/components/MobileBlocker.vue +5 -5
  17. package/components/MobileLoginPicker.vue +13 -13
  18. package/components/Modal/Base.vue +6 -0
  19. package/components/Modal/Confirm.vue +89 -0
  20. package/components/Modal/DeleteConfirm.vue +14 -31
  21. package/components/Modal/Form.vue +76 -0
  22. package/components/Modal/Host.vue +58 -0
  23. package/components/Modal/Prompt.vue +74 -0
  24. package/components/Modal.vue +119 -34
  25. package/components/Organization/Picker.vue +3 -3
  26. package/components/Table/Standard.vue +198 -25
  27. package/components/Table/index.vue +169 -369
  28. package/components/Table/views/ViewCards.vue +74 -0
  29. package/components/Table/views/ViewDatabase.vue +236 -0
  30. package/components/Table/views/ViewTable.vue +299 -0
  31. package/components/Toast/Alert.vue +16 -111
  32. package/components/Toast/Container.vue +4 -29
  33. package/components/Toast/Notification.vue +8 -43
  34. package/components/Toast/Process.vue +8 -86
  35. package/components/Toast/Stack.vue +59 -0
  36. package/components/Toast/Standard.vue +115 -0
  37. package/components/Tree/Standard.vue +49 -4
  38. package/components/Workflow/Board.vue +356 -0
  39. package/components/Workflow/Builder.vue +454 -0
  40. package/components/Workflow/Form.vue +526 -0
  41. package/components/Workflow/Viewer.vue +225 -0
  42. package/components/Workflow/edges/TransitionEdge.vue +84 -0
  43. package/components/Workflow/nodes/StepNode.vue +88 -0
  44. package/composables/useAuth.js +1 -1
  45. package/composables/useClipboard.js +79 -0
  46. package/composables/useConfetti.js +194 -0
  47. package/composables/useContext.js +53 -5
  48. package/composables/useFile.js +189 -0
  49. package/composables/useMobileGuard.ts +21 -21
  50. package/composables/useModal.js +71 -0
  51. package/composables/useOrganization.ts +3 -3
  52. package/composables/useToast.js +38 -65
  53. package/composables/useWorkflow/form.js +162 -0
  54. package/composables/useWorkflow/index.js +18 -0
  55. package/composables/useWorkflow/view.js +220 -0
  56. package/composables/useWorkflowLayout.js +162 -0
  57. package/css/tokens.css +10 -10
  58. package/docs/superpowers/plans/2026-05-27-nuxt-apps-to-contexts-rename.md +630 -0
  59. package/docs/superpowers/specs/2026-05-27-nuxt-apps-to-contexts-rename-design.md +113 -0
  60. package/middleware/{03.apps.global.ts → 03.contexts.global.ts} +25 -25
  61. package/nuxt.config.ts +1 -1
  62. package/package.json +13 -8
  63. package/stores/modal.js +48 -0
  64. package/stores/toast.js +156 -117
  65. package/composables/useApp.ts +0 -46
@@ -0,0 +1,630 @@
1
+ # innertia-nuxt: apps → contexts Rename — Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Rename the "apps" concept to "contexts" throughout innertia-nuxt, aligning the frontend library with the already-renamed innertia-laravel backend.
6
+
7
+ **Architecture:** Pure rename — zero behavior changes. Config key `innertia.apps` → `innertia.contexts`, types `AppDefinition`/`AppMobileMode` → `ContextDefinition`/`ContextMobileMode`, composable `useApp()` merged into `useContext()`, middleware file renamed, login body field corrected. No backward-compat aliases.
8
+
9
+ **Tech Stack:** Nuxt 4, Vue 3, TypeScript, Pinia. No test suite in this repo — verification is via `grep` for stale references.
10
+
11
+ **Working directory:** `/Users/guillermofarias/Innertia/Dev/workspace/libraries/innertia-nuxt`
12
+
13
+ **Spec:** `docs/superpowers/specs/2026-05-27-nuxt-apps-to-contexts-rename-design.md`
14
+
15
+ ---
16
+
17
+ ## File Map
18
+
19
+ | File | Action |
20
+ |------|--------|
21
+ | `app.config.ts` | Rename type `AppDefinition` → `ContextDefinition`, `AppMobileMode` → `ContextMobileMode`, config key `innertia.apps` → `innertia.contexts` |
22
+ | `composables/useContext.js` | Absorb all logic from `useApp.ts`; now also reads from `innertia.contexts` and returns `current`, `all`, `accessible`, `canAccess`, `contexts` |
23
+ | `composables/useApp.ts` | **Delete** |
24
+ | `composables/useAuth.js` | Fix login body: `{ app: context }` → `{ context }` |
25
+ | `middleware/03.apps.global.ts` | **Rename** to `03.contexts.global.ts`; update import + internal variable references |
26
+ | `composables/useOrganization.ts` | `useApp()` → `useContext()` |
27
+ | `composables/useMobileGuard.ts` | `useApp()` → `useContext()`, `AppDefinition` → `ContextDefinition`, cookie key renamed |
28
+ | `CHANGELOG.md` | Create — document BREAKING CHANGES |
29
+
30
+ ---
31
+
32
+ ### Task 1: `app.config.ts` — rename types and config key
33
+
34
+ **Files:**
35
+ - Modify: `app.config.ts`
36
+
37
+ - [ ] **Step 1: Replace `innertia.apps` config key with `innertia.contexts`**
38
+
39
+ Find line (currently ~128):
40
+ ```ts
41
+ apps: {} as Record<string, AppDefinition>,
42
+ ```
43
+ Replace with:
44
+ ```ts
45
+ /**
46
+ * Declaración de contextos del producto.
47
+ * Cada contexto define un prefijo de URL que mapea a un contexto del backend
48
+ * (matching con `availableContexts` que devuelve `auth/me`).
49
+ */
50
+ contexts: {} as Record<string, ContextDefinition>,
51
+ ```
52
+
53
+ Also update the comment block just above it (currently ~122–127):
54
+ ```ts
55
+ /**
56
+ * Declaración de "apps" (contextos) del producto.
57
+ * Cada app define un prefijo de URL que mapea a un contexto del backend
58
+ * (matching con `availableContexts` que devuelve `auth/me`).
59
+ */
60
+ apps: {} as Record<string, AppDefinition>,
61
+ ```
62
+
63
+ - [ ] **Step 2: Rename `AppMobileMode` type**
64
+
65
+ Find line (~147):
66
+ ```ts
67
+ export type AppMobileMode = 'allow' | 'block' | 'redirect'
68
+ ```
69
+ Replace with:
70
+ ```ts
71
+ export type ContextMobileMode = 'allow' | 'block' | 'redirect'
72
+ ```
73
+
74
+ - [ ] **Step 3: Rename `AppDefinition` interface and update its internal reference to `AppMobileMode`**
75
+
76
+ Find (~178–199):
77
+ ```ts
78
+ export interface AppDefinition {
79
+ /** Prefijo de URL — la ruta debe empezar con esto para considerarse "dentro" del app. */
80
+ path: string
81
+ /** Clave del contexto que matchea con `availableContexts` del backend. */
82
+ context: string
83
+ /** Label visible en pickers, dropdowns, etc. */
84
+ label: string
85
+ /** Descripción opcional para el picker mobile. */
86
+ description?: string
87
+ /** Nombre del icono de @tabler/icons-vue (sin "Icon"). */
88
+ icon?: string
89
+ /** Ruta de login del app. */
90
+ loginPath: string
91
+ /** Ruta home (post-login). */
92
+ home: string
93
+ /** Política de uso en mobile. */
94
+ mobile: {
95
+ mode: AppMobileMode
96
+ /** Solo si mode === 'redirect' — adónde mandar desde mobile. */
97
+ redirectTo?: string
98
+ }
99
+ }
100
+ ```
101
+ Replace with:
102
+ ```ts
103
+ export interface ContextDefinition {
104
+ /** Prefijo de URL — la ruta debe empezar con esto para considerarse "dentro" del contexto. */
105
+ path: string
106
+ /** Clave del contexto que matchea con `availableContexts` del backend. */
107
+ context: string
108
+ /** Label visible en pickers, dropdowns, etc. */
109
+ label: string
110
+ /** Descripción opcional para el picker mobile. */
111
+ description?: string
112
+ /** Nombre del icono de @tabler/icons-vue (sin "Icon"). */
113
+ icon?: string
114
+ /** Ruta de login del contexto. */
115
+ loginPath: string
116
+ /** Ruta home (post-login). */
117
+ home: string
118
+ /** Política de uso en mobile. */
119
+ mobile: {
120
+ mode: ContextMobileMode
121
+ /** Solo si mode === 'redirect' — adónde mandar desde mobile. */
122
+ redirectTo?: string
123
+ }
124
+ }
125
+ ```
126
+
127
+ - [ ] **Step 4: Verify no remaining `AppDefinition` or `AppMobileMode` references in `app.config.ts`**
128
+
129
+ Run:
130
+ ```bash
131
+ grep -n "AppDefinition\|AppMobileMode" app.config.ts
132
+ ```
133
+ Expected: no output (zero matches).
134
+
135
+ - [ ] **Step 5: Commit**
136
+
137
+ ```bash
138
+ git add app.config.ts
139
+ git commit -m "refactor(contexts): rename AppDefinition→ContextDefinition, AppMobileMode→ContextMobileMode, innertia.apps→innertia.contexts in app.config"
140
+ ```
141
+
142
+ ---
143
+
144
+ ### Task 2: `useContext.js` — absorb `useApp` logic
145
+
146
+ **Files:**
147
+ - Modify: `composables/useContext.js`
148
+
149
+ The current `useContext.js` handles runtime context switching. We extend it to also expose the config-level definitions (previously in `useApp.ts`), reading from `appConfig.innertia.contexts`.
150
+
151
+ - [ ] **Step 1: Replace the full contents of `composables/useContext.js`**
152
+
153
+ ```js
154
+ // useAuthStore, useApi, useAuth auto-imported
155
+ import { computed } from 'vue'
156
+ import type { ContextDefinition } from '../app.config'
157
+
158
+ export function useContext() {
159
+ const route = useRoute()
160
+ const authStore = useAuthStore()
161
+ const appConfig = useAppConfig()
162
+ const api = useApi()
163
+ const { fetchMe } = useAuth()
164
+
165
+ // ── Config-level: definiciones declaradas en appConfig.innertia.contexts ───
166
+
167
+ /** Diccionario de contextos declarados por el producto. */
168
+ const contexts = computed<Record<string, ContextDefinition>>(() =>
169
+ (appConfig.innertia?.contexts ?? {}) as Record<string, ContextDefinition>
170
+ )
171
+
172
+ /** Todos los contextos declarados, en orden de declaración. */
173
+ const all = computed<ContextDefinition[]>(() => Object.values(contexts.value))
174
+
175
+ /** Contexto actual según URL. `null` si la URL no cae en ningún contexto declarado. */
176
+ const current = computed<ContextDefinition | null>(() => {
177
+ return all.value.find(ctx =>
178
+ route.path === ctx.path || route.path.startsWith(ctx.path + '/')
179
+ ) ?? null
180
+ })
181
+
182
+ /** Contextos a los que el usuario autenticado tiene acceso (filtrado por `availableContexts`). */
183
+ const accessible = computed<ContextDefinition[]>(() => {
184
+ const ctxs = (authStore.availableContexts ?? []) as string[]
185
+ return all.value.filter(ctx => ctxs.includes(ctx.context))
186
+ })
187
+
188
+ /** Helper: ¿el usuario puede acceder a este contexto (por clave de config)? */
189
+ function canAccess(key: string): boolean {
190
+ const ctx = contexts.value[key]
191
+ if (!ctx) return false
192
+ return ((authStore.availableContexts ?? []) as string[]).includes(ctx.context)
193
+ }
194
+
195
+ // ── Runtime: contexto activo + switching ───────────────────────────────────
196
+
197
+ const currentContext = computed(() => authStore.currentContext)
198
+ const availableContexts = computed(() => authStore.availableContexts)
199
+
200
+ /**
201
+ * Check whether user has permission to switch to targetContext.
202
+ * Returns:
203
+ * { success: false, reason: 'no_permission' } — user cannot switch
204
+ * { success: true, requiresConfirmation: true } — show confirmation UI
205
+ */
206
+ async function switchContext(targetContext) {
207
+ const data = await api.get(`auth/context/${targetContext}/check`)
208
+ if (!data.hasAccess) {
209
+ return { success: false, reason: 'no_permission' }
210
+ }
211
+ return { success: true, requiresConfirmation: true }
212
+ }
213
+
214
+ /**
215
+ * Execute the context switch after user confirmation.
216
+ * Updates store and reloads permissions via fetchMe.
217
+ */
218
+ async function confirmSwitch(targetContext) {
219
+ authStore.setCurrentContext(targetContext)
220
+ await fetchMe()
221
+ return { success: true }
222
+ }
223
+
224
+ /**
225
+ * Quick synchronous check — is this context in the available list?
226
+ */
227
+ function hasAccessToContext(context) {
228
+ return authStore.availableContexts.includes(context)
229
+ }
230
+
231
+ return {
232
+ // config-level
233
+ contexts,
234
+ all,
235
+ current,
236
+ accessible,
237
+ canAccess,
238
+ // runtime
239
+ currentContext,
240
+ availableContexts,
241
+ switchContext,
242
+ confirmSwitch,
243
+ hasAccessToContext,
244
+ }
245
+ }
246
+ ```
247
+
248
+ - [ ] **Step 2: Verify the file saved correctly**
249
+
250
+ ```bash
251
+ grep -n "innertia.contexts\|ContextDefinition\|contexts\|current\|accessible" composables/useContext.js | head -20
252
+ ```
253
+ Expected: lines showing `innertia.contexts`, `ContextDefinition`, and all the returned properties.
254
+
255
+ - [ ] **Step 3: Commit**
256
+
257
+ ```bash
258
+ git add composables/useContext.js
259
+ git commit -m "refactor(contexts): useContext absorbs useApp — exposes current, all, accessible, canAccess, contexts from innertia.contexts config"
260
+ ```
261
+
262
+ ---
263
+
264
+ ### Task 3: Delete `useApp.ts`
265
+
266
+ **Files:**
267
+ - Delete: `composables/useApp.ts`
268
+
269
+ - [ ] **Step 1: Delete the file**
270
+
271
+ ```bash
272
+ git rm composables/useApp.ts
273
+ ```
274
+
275
+ - [ ] **Step 2: Commit**
276
+
277
+ ```bash
278
+ git commit -m "refactor(contexts): remove useApp.ts — functionality merged into useContext"
279
+ ```
280
+
281
+ ---
282
+
283
+ ### Task 4: `useAuth.js` — fix login body field
284
+
285
+ **Files:**
286
+ - Modify: `composables/useAuth.js`
287
+
288
+ The `performLogin` function currently sends `app: context` in the POST body. The backend (innertia-laravel) now expects the field to be named `context`.
289
+
290
+ - [ ] **Step 1: Fix the `performLogin` call**
291
+
292
+ Find line (~17):
293
+ ```js
294
+ const data = await api.post(`${context}/auth/login`, { email, password, app: context })
295
+ ```
296
+ Replace with:
297
+ ```js
298
+ const data = await api.post(`${context}/auth/login`, { email, password, context })
299
+ ```
300
+
301
+ - [ ] **Step 2: Verify**
302
+
303
+ ```bash
304
+ grep -n "app:" composables/useAuth.js
305
+ ```
306
+ Expected: no output (zero matches — the old `app: context` field is gone).
307
+
308
+ - [ ] **Step 3: Commit**
309
+
310
+ ```bash
311
+ git add composables/useAuth.js
312
+ git commit -m "fix(contexts): send context field instead of app in performLogin body"
313
+ ```
314
+
315
+ ---
316
+
317
+ ### Task 5: Rename and update middleware
318
+
319
+ **Files:**
320
+ - Delete: `middleware/03.apps.global.ts`
321
+ - Create: `middleware/03.contexts.global.ts`
322
+
323
+ - [ ] **Step 1: Create `middleware/03.contexts.global.ts` with updated content**
324
+
325
+ ```ts
326
+ /**
327
+ * Middleware global que se ejecuta en cada navegación.
328
+ *
329
+ * Lee la declaración de contextos desde `appConfig.innertia.contexts`. Si el
330
+ * producto no declara contextos, este middleware no hace nada (feature inactivo).
331
+ *
332
+ * Cuando hay contextos declarados, resuelve qué contexto está intentando acceder
333
+ * el usuario según el prefijo de URL y:
334
+ * 1. Si la URL no cae en ningún contexto declarado → pasa (deja seguir)
335
+ * 2. Si la URL es la ruta de login del contexto → pasa (sin chequeo de permisos)
336
+ * 3. Si el usuario no está autenticado → deja que el middleware `auth` redirija
337
+ * 4. Si `availableContexts` está vacío (SSR sin hidratar) → intenta fetchMe()
338
+ * y si falla, pasa sin redirigir (mejor renderizar que redirigir mal)
339
+ * 5. Si el usuario está autenticado pero NO tiene acceso al contexto →
340
+ * redirige al primer contexto accesible (o a su loginPath si no tiene ninguno)
341
+ * 6. Si todo OK pero `currentContext !== targetContext.context` → sincroniza el
342
+ * contexto en el authStore y recarga permisos vía fetchMe()
343
+ *
344
+ * Esto es lo que permite el "cambio de contexto implícito por URL" sin botón.
345
+ *
346
+ * Numeración 03. para correr DESPUÉS de los middlewares saas
347
+ * (01.detect-subdomain, 02.validate-tenant).
348
+ */
349
+ import type { ContextDefinition } from '../app.config'
350
+
351
+ export default defineNuxtRouteMiddleware(async (to) => {
352
+ const appConfig = useAppConfig()
353
+ const contexts = (appConfig.innertia?.contexts ?? {}) as Record<string, ContextDefinition>
354
+ const list = Object.values(contexts)
355
+
356
+ // Si el producto no declaró contextos, el feature está inactivo
357
+ if (list.length === 0) return
358
+
359
+ // ¿La ruta cae bajo algún contexto?
360
+ const targetContext = list.find(ctx =>
361
+ to.path === ctx.path || to.path.startsWith(ctx.path + '/')
362
+ )
363
+
364
+ if (!targetContext) return // ruta pública / fuera de contextos
365
+
366
+ // La ruta de login del propio contexto no requiere permisos
367
+ if (to.path === targetContext.loginPath) return
368
+
369
+ const authStore = useAuthStore()
370
+
371
+ // Si no está autenticado, deja que el middleware `auth` de la página haga el redirect
372
+ if (!authStore.isAuthenticated()) return
373
+
374
+ // ── Hidratación defensiva ─────────────────────────────────────────────────
375
+ // `availableContexts` se persiste en localStorage, que NO se lee en SSR.
376
+ // El plugin auth-init hace fetchMe en SSR pero puede fallar silenciosamente
377
+ // (ej. tenant no resuelto). Si llegamos con token válido pero array vacío,
378
+ // intentamos cargar los contextos antes de decidir el redirect.
379
+ if ((authStore.availableContexts ?? []).length === 0) {
380
+ try {
381
+ const { fetchMe } = useAuth()
382
+ await fetchMe()
383
+ } catch {
384
+ // no podemos resolver — pasamos: mejor renderizar la página que redirigir mal
385
+ return
386
+ }
387
+ }
388
+
389
+ const userContexts = (authStore.availableContexts ?? []) as string[]
390
+
391
+ // Si tras hidratar seguimos sin contextos, NO redirigimos.
392
+ if (userContexts.length === 0) return
393
+
394
+ // ¿Tiene acceso al contexto?
395
+ if (!userContexts.includes(targetContext.context)) {
396
+ const fallback = list.find(ctx => userContexts.includes(ctx.context))
397
+ if (fallback) {
398
+ return navigateTo(fallback.home, { replace: true })
399
+ }
400
+ return navigateTo(list[0]?.loginPath ?? '/', { replace: true })
401
+ }
402
+
403
+ // Sincronizar currentContext silenciosamente si cambia
404
+ if (authStore.currentContext !== targetContext.context) {
405
+ authStore.setCurrentContext(targetContext.context)
406
+ try {
407
+ const { fetchMe } = useAuth()
408
+ await fetchMe()
409
+ } catch {
410
+ // best-effort
411
+ }
412
+ }
413
+ })
414
+ ```
415
+
416
+ - [ ] **Step 2: Remove the old middleware file**
417
+
418
+ ```bash
419
+ git rm middleware/03.apps.global.ts
420
+ ```
421
+
422
+ - [ ] **Step 3: Stage and commit**
423
+
424
+ ```bash
425
+ git add middleware/03.contexts.global.ts
426
+ git commit -m "refactor(contexts): rename 03.apps.global→03.contexts.global, innertia.apps→innertia.contexts, AppDefinition→ContextDefinition"
427
+ ```
428
+
429
+ ---
430
+
431
+ ### Task 6: `useOrganization.ts` + `useMobileGuard.ts` — swap `useApp()` for `useContext()`
432
+
433
+ **Files:**
434
+ - Modify: `composables/useOrganization.ts`
435
+ - Modify: `composables/useMobileGuard.ts`
436
+
437
+ - [ ] **Step 1: Update `useOrganization.ts` — change `useApp()` to `useContext()`**
438
+
439
+ Find line 13:
440
+ ```ts
441
+ const { current: currentApp } = useApp()
442
+ ```
443
+ Replace with:
444
+ ```ts
445
+ const { current: currentApp } = useContext()
446
+ ```
447
+
448
+ (The local alias `currentApp` is kept — it's used on line 23 as `currentApp.value?.context`. No other changes needed in this file.)
449
+
450
+ - [ ] **Step 2: Verify `useOrganization.ts` — no remaining `useApp` references**
451
+
452
+ ```bash
453
+ grep -n "useApp" composables/useOrganization.ts
454
+ ```
455
+ Expected: no output.
456
+
457
+ - [ ] **Step 3: Update `useMobileGuard.ts` — full replacement**
458
+
459
+ Replace the full file with:
460
+
461
+ ```ts
462
+ import { useMediaQuery } from '@vueuse/core'
463
+ import type { ContextDefinition } from '../app.config'
464
+
465
+ const PICKER_CHOICE_COOKIE = 'innertia_mobile_context_choice'
466
+ const PICKER_COOKIE_MAX_AGE = 60 * 60 * 24 * 30 // 30 días
467
+
468
+ /**
469
+ * Detección reactiva de viewport mobile + lógica de landing.
470
+ *
471
+ * Reglas para `landing` (qué mostrar cuando el usuario entra a `/` en mobile):
472
+ * - 0 contextos mobile-friendly → blocker "abre en escritorio"
473
+ * - 1 contexto mobile-friendly → redirect directo al login de ese contexto
474
+ * - 2+ contextos mobile-friendly → picker (con cookie para recordar última elección)
475
+ *
476
+ * El breakpoint se lee desde `appConfig.innertia.mobile.breakpoint` (default 1024).
477
+ */
478
+ export function useMobileGuard() {
479
+ const { all, current, accessible } = useContext()
480
+ const authStore = useAuthStore()
481
+ const appConfig = useAppConfig()
482
+
483
+ const breakpoint: number = appConfig.innertia?.mobile?.breakpoint ?? 1024
484
+ const isMobile = useMediaQuery(`(max-width: ${breakpoint - 1}px)`)
485
+
486
+ /** Contextos con mode 'allow' en mobile. */
487
+ const mobileApps = computed<ContextDefinition[]>(() =>
488
+ all.value.filter(a => a.mobile?.mode === 'allow')
489
+ )
490
+
491
+ /** Contextos mobile-friendly + accesibles para el usuario autenticado. */
492
+ const mobileAccessibleApps = computed<ContextDefinition[]>(() =>
493
+ accessible.value.filter(a => a.mobile?.mode === 'allow')
494
+ )
495
+
496
+ /** ¿El contexto actual está bloqueado en mobile? */
497
+ const isCurrentAppBlocked = computed<boolean>(() => {
498
+ if (!isMobile.value) return false
499
+ if (!current.value) return false
500
+ return current.value.mobile?.mode === 'block'
501
+ })
502
+
503
+ /**
504
+ * Para el blocker: si el usuario está autenticado y tiene OTRO contexto mobile-friendly,
505
+ * lo ofrecemos como fallback ("continuar en X").
506
+ */
507
+ const mobileFallbackApp = computed<ContextDefinition | null>(() => {
508
+ if (!authStore.isAuthenticated()) return null
509
+ return mobileAccessibleApps.value[0] ?? null
510
+ })
511
+
512
+ // ── Cookie helpers para recordar última elección del picker ─────────────────
513
+ function rememberPickerChoice(contextKey: string) {
514
+ if (!(appConfig.innertia?.mobile?.rememberChoice ?? true)) return
515
+ const cookie = useCookie<string | null>(PICKER_CHOICE_COOKIE, {
516
+ maxAge: PICKER_COOKIE_MAX_AGE,
517
+ sameSite: 'lax',
518
+ })
519
+ cookie.value = contextKey
520
+ }
521
+
522
+ function getRememberedPickerChoice(): string | null {
523
+ const cookie = useCookie<string | null>(PICKER_CHOICE_COOKIE)
524
+ return cookie.value ?? null
525
+ }
526
+
527
+ function clearRememberedPickerChoice() {
528
+ const cookie = useCookie<string | null>(PICKER_CHOICE_COOKIE)
529
+ cookie.value = null
530
+ }
531
+
532
+ return {
533
+ isMobile,
534
+ mobileApps,
535
+ mobileAccessibleApps,
536
+ isCurrentAppBlocked,
537
+ mobileFallbackApp,
538
+ rememberPickerChoice,
539
+ getRememberedPickerChoice,
540
+ clearRememberedPickerChoice,
541
+ }
542
+ }
543
+ ```
544
+
545
+ - [ ] **Step 4: Verify no remaining `useApp` or `AppDefinition` references in both files**
546
+
547
+ ```bash
548
+ grep -n "useApp\|AppDefinition\|AppMobileMode" composables/useOrganization.ts composables/useMobileGuard.ts
549
+ ```
550
+ Expected: no output.
551
+
552
+ - [ ] **Step 5: Commit**
553
+
554
+ ```bash
555
+ git add composables/useOrganization.ts composables/useMobileGuard.ts
556
+ git commit -m "refactor(contexts): useOrganization + useMobileGuard — useApp()→useContext(), AppDefinition→ContextDefinition"
557
+ ```
558
+
559
+ ---
560
+
561
+ ### Task 7: Docs — CHANGELOG + final verification
562
+
563
+ **Files:**
564
+ - Create: `CHANGELOG.md`
565
+
566
+ - [ ] **Step 1: Create `CHANGELOG.md`**
567
+
568
+ ```bash
569
+ cat > CHANGELOG.md << 'EOF'
570
+ # Changelog
571
+
572
+ All notable changes to `@innertia-solutions/innertia-nuxt` are documented here.
573
+
574
+ ## [Unreleased]
575
+
576
+ ### BREAKING CHANGES
577
+
578
+ #### apps → contexts rename
579
+
580
+ Aligns frontend with innertia-laravel which already renamed the concept.
581
+
582
+ **Config (`app.config.ts` / `nuxt.config.ts` in your product):**
583
+ ```diff
584
+ - innertia.apps: { backoffice: { ... }, technician: { ... } }
585
+ + innertia.contexts: { backoffice: { ... }, technician: { ... } }
586
+ ```
587
+
588
+ **Types:**
589
+ ```diff
590
+ - import type { AppDefinition } from '@innertia-solutions/innertia-nuxt/app.config'
591
+ + import type { ContextDefinition } from '@innertia-solutions/innertia-nuxt/app.config'
592
+
593
+ - import type { AppMobileMode } from '@innertia-solutions/innertia-nuxt/app.config'
594
+ + import type { ContextMobileMode } from '@innertia-solutions/innertia-nuxt/app.config'
595
+ ```
596
+
597
+ **Composables:**
598
+ ```diff
599
+ - const { current, all, accessible, canAccess } = useApp()
600
+ + const { current, all, accessible, canAccess } = useContext()
601
+ # useContext() now also returns: contexts (dict), currentContext, availableContexts,
602
+ # switchContext(), confirmSwitch(), hasAccessToContext()
603
+ ```
604
+
605
+ `useApp()` has been removed. Use `useContext()` for all context/app logic.
606
+
607
+ **Login body field:**
608
+
609
+ The `performLogin()` function in `useAuth` now sends `context` instead of `app` in
610
+ the POST body — matching what innertia-laravel now expects.
611
+ EOF
612
+ ```
613
+
614
+ - [ ] **Step 2: Final grep — verify no stale references remain in the entire library**
615
+
616
+ ```bash
617
+ grep -r "innertia\.apps\|AppDefinition\|AppMobileMode\|useApp()\|'app:'\|\"app:\"" \
618
+ --include="*.ts" --include="*.js" --include="*.vue" \
619
+ --exclude-dir=".nuxt" --exclude-dir="node_modules" \
620
+ .
621
+ ```
622
+ Expected: **no output**. If any matches appear, fix them before committing.
623
+
624
+ - [ ] **Step 3: Commit**
625
+
626
+ ```bash
627
+ git add CHANGELOG.md
628
+ git commit -m "docs(contexts): add CHANGELOG with apps→contexts BREAKING CHANGE"
629
+ ```
630
+ EOF