@abgov/nx-adsp 13.18.0-beta.1 → 13.18.0-beta.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "13.18.0-beta.1",
3
+ "version": "13.18.0-beta.3",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -30,8 +30,13 @@ executors read options from `project.json` and do not prompt.
30
30
 
31
31
  | File | Purpose |
32
32
  |------|---------|
33
- | `src/main.ts` | Entry — registers Pinia, Router, and Keycloak plugin |
34
- | `src/App.vue` | Shell `AppHeader` with sign-in/out in its `utilities` slot, hero banner, `<RouterView>` wrapped in `AppLayout`, `AppFooter` |
33
+ | `src/main.ts` | Entry — registers Pinia, Router, and Keycloak plugin (incl. `onAuthRefreshError`, which flags the session store as expired) |
34
+ <% if (layout === 'internal') { %>
35
+ | `src/App.vue` | Shell — `AppSideMenu` (side-menu shell, Sign in/out as an account item), `SessionExpiredBanner`, `<RouterView>` wrapped in `AppLayout` |
36
+ <% } else { %>
37
+ | `src/App.vue` | Shell — `AppHeader` with sign-in/out in its `utilities` slot, `SessionExpiredBanner`, hero banner, `<RouterView>` wrapped in `AppLayout`, `AppFooter` |
38
+ <% } %>
39
+ | `src/stores/session.ts` | Pinia store — `expired` flag behind `SessionExpiredBanner`, set from `main.ts`'s `onAuthRefreshError` hook |
35
40
  | `src/router/index.ts` | Routes — `/protected` guarded with `requiresAuth` meta |
36
41
  | `src/views/HomeView.vue` | Public page — calls public and private APIs |
37
42
  | `src/views/ProtectedView.vue` | Authenticated page — shows user info from token |
@@ -154,12 +159,27 @@ across every Vue app in this workspace — fix or extend a wrapper once in
154
159
  > stopgap until GoA DS ships an official `@abgov/vue-components`. When it lands,
155
160
  > delete `primitives/` and repoint imports at that package — the names/props are
156
161
  > intended to match, so it's a scope swap. This does **not** apply to the
157
- > `AppHeader`/`AppLayout`/`AppFooter`/`SessionExpiredBanner` shell components
158
- > below — those live in the same library's `patterns/` folder and are permanent
159
- > (app-shell composition has no equivalent in a design-system package).
162
+ > `AppHeader`/`AppLayout`/`AppFooter`/`AppSideMenu`/`SessionExpiredBanner` shell
163
+ > components below — those live in the same library's `patterns/` folder and are
164
+ > permanent (app-shell composition has no equivalent in a design-system package).
160
165
 
161
166
  ### App shell components
162
167
 
168
+ This app was generated with **`--layout=<%= layout %>`**.
169
+ <% if (layout === 'internal') { %>
170
+ `App.vue` is a staff-facing, side-menu shell built from shared components imported
171
+ from **`<%= goaImportPath %>`**, not hand-rolled markup:
172
+
173
+ | Component | Purpose |
174
+ |---|---|
175
+ | `AppSideMenu` | `goa-work-side-menu`. Takes `heading`/`primaryItems`/`secondaryItems`/`accountItems` props (each item: `{ label, to?, icon?, badge?, current? }`) and emits `itemClick` — it doesn't know about routing or auth, so `App.vue` computes `current`/handles the click itself. `App.vue` currently only populates `accountItems` (a single Sign in/out item); add real nav items to `primaryItems`/`secondaryItems` as routes are added |
176
+ | `AppLayout` | The content gutter every view renders inside (see the key files table). Also renders the skip-to-main-content link |
177
+ | `SessionExpiredBanner` | A `v-model:show` banner with `signIn`/`dismiss` emits. Bound to the `useSessionStore()` Pinia store (`src/stores/session.ts`), which `main.ts`'s `onAuthRefreshError` hook flips when the refresh token itself has expired |
178
+
179
+ There's no `AppHeader`/`AppFooter`/hero banner in this layout — `AppSideMenu` is the
180
+ whole top-level shell, matching the real GoA "internal portal" pattern (no public
181
+ marketing chrome around a staff tool).
182
+ <% } else { %>
163
183
  `App.vue` is built from four shared components imported from
164
184
  **`<%= goaImportPath %>`**, not hand-rolled markup:
165
185
 
@@ -168,11 +188,17 @@ across every Vue app in this workspace — fix or extend a wrapper once in
168
188
  | `AppHeader` | `goa-microsite-header` + `goa-app-header`. Takes a `heading` prop; put account/sign-in actions in its `utilities` slot (a bare child with no slot is silently dropped by `goa-app-header`) |
169
189
  | `AppLayout` | The content gutter every view renders inside (see the key files table). Also renders the skip-to-main-content link |
170
190
  | `AppFooter` | `goa-app-footer` with the standard GoA nav/meta links (Services/Contact/Terms of Use, Privacy/Disclaimer/Accessibility) |
171
- | `SessionExpiredBanner` | A `v-model:show` banner with `signIn`/`dismiss` emits available in the shared library, not yet wired into this app's `App.vue`. Wiring it to a real Keycloak session-expiry signal needs an `onAuthLogout` callback added to `main.ts`'s Keycloak plugin config; do that before using it |
172
-
191
+ | `SessionExpiredBanner` | A `v-model:show` banner with `signIn`/`dismiss` emits. Bound to the `useSessionStore()` Pinia store (`src/stores/session.ts`), which `main.ts`'s `onAuthRefreshError` hook flips when the refresh token itself has expired |
192
+ <% } %>
173
193
  These are shared across every Vue app in this workspace — fix or extend one once
174
194
  in `libs/vue-components/src/lib/patterns/`, the same way as the `Goab*` wrappers.
175
195
 
196
+ A second, **`--layout=<%= layout === 'internal' ? 'header' : 'internal' %>`** frontend
197
+ can be scaffolded against the same backend with `nx g @abgov/nx-adsp:vue-app <name>
198
+ --pairedProject <%= pairedProject || 'your-backend-project' %> --layout=<%= layout === 'internal' ? 'header' : 'internal' %>`
199
+ — a public-facing app and a staff-facing one sharing one API, without a separate
200
+ composite generator.
201
+
176
202
  ## Adding a view
177
203
 
178
204
  1. Create `src/views/MyFeatureView.vue` as a `<script setup>` SFC. **Don't add your
@@ -16,8 +16,10 @@ vi.mock('@dsb-norge/vue-keycloak-js', async () => {
16
16
  });
17
17
 
18
18
  import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
19
+ import { createPinia } from 'pinia';
19
20
  import { createMemoryHistory, createRouter } from 'vue-router';
20
21
  import App from './App.vue';
22
+ import { useSessionStore } from './stores/session';
21
23
 
22
24
  // App.vue reads useRoute() for the layout-width feature (route.meta.layout,
23
25
  // default 'page') — that needs a real router installed via `global.plugins`,
@@ -32,10 +34,37 @@ const router = createRouter({
32
34
  history: createMemoryHistory(),
33
35
  routes: [{ path: '/', component: { template: '<div />' } }],
34
36
  });
37
+ <% if (layout === 'internal') { %>
38
+ describe('App shell (internal / side-menu)', () => {
39
+ it('shows a Sign in account item when unauthenticated', () => {
40
+ const wrapper = mount(App, {
41
+ global: { plugins: [router, createPinia()], stubs: { RouterView: true } },
42
+ });
43
+ const item = wrapper.find('goa-work-side-menu-item[label="Sign in"]');
44
+ expect(item.exists()).toBe(true);
45
+ });
35
46
 
47
+ it('clicking the account item calls keycloak.login (guards against lost reactivity)', async () => {
48
+ // Same bug class as the header shell: destructuring useKeycloak() would
49
+ // freeze `keycloak` at undefined, so login() would be a permanent no-op.
50
+ const kc = useKeycloak();
51
+ const wrapper = mount(App, {
52
+ global: { plugins: [router, createPinia()], stubs: { RouterView: true } },
53
+ });
54
+ // AppSideMenu binds @click.prevent directly on goa-work-side-menu-item (not
55
+ // an underscore-prefixed event, unlike the goa-* form elements) -- trigger
56
+ // a plain 'click'.
57
+ const item = wrapper.find('goa-work-side-menu-item[label="Sign in"]');
58
+ await item.trigger('click');
59
+ expect(kc.keycloak?.login).toHaveBeenCalled();
60
+ });
61
+ });
62
+ <% } else { %>
36
63
  describe('App shell header', () => {
37
64
  it('shows Sign in when unauthenticated', () => {
38
- const wrapper = mount(App, { global: { plugins: [router], stubs: { RouterView: true } } });
65
+ const wrapper = mount(App, {
66
+ global: { plugins: [router, createPinia()], stubs: { RouterView: true } },
67
+ });
39
68
  const group = wrapper.find('goa-button-group');
40
69
  expect(group.exists()).toBe(true);
41
70
  expect(group.html()).toContain('Sign in');
@@ -45,9 +74,47 @@ describe('App shell header', () => {
45
74
  // The bug: destructuring useKeycloak() froze `keycloak` at undefined, so
46
75
  // login() was a permanent no-op. Reactive access must reach the instance.
47
76
  const kc = useKeycloak();
48
- const wrapper = mount(App, { global: { plugins: [router], stubs: { RouterView: true } } });
77
+ const wrapper = mount(App, {
78
+ global: { plugins: [router, createPinia()], stubs: { RouterView: true } },
79
+ });
49
80
  const btn = wrapper.findAll('goa-button').find((b) => b.text().includes('Sign in'));
50
81
  await btn?.trigger('_click');
51
82
  expect(kc.keycloak?.login).toHaveBeenCalled();
52
83
  });
53
84
  });
85
+ <% } %>
86
+ describe('App shell session-expired banner', () => {
87
+ it('is hidden until the session store flags an expired session', async () => {
88
+ const pinia = createPinia();
89
+ const wrapper = mount(App, {
90
+ global: { plugins: [router, pinia], stubs: { RouterView: true } },
91
+ });
92
+ expect(wrapper.find('.session-expired-banner').exists()).toBe(false);
93
+
94
+ useSessionStore(pinia).markExpired();
95
+ await wrapper.vm.$nextTick();
96
+ expect(wrapper.find('.session-expired-banner').exists()).toBe(true);
97
+ });
98
+
99
+ it('Sign in on the banner dismisses it and calls keycloak.login', async () => {
100
+ const kc = useKeycloak();
101
+ const pinia = createPinia();
102
+ const wrapper = mount(App, {
103
+ global: { plugins: [router, pinia], stubs: { RouterView: true } },
104
+ });
105
+ useSessionStore(pinia).markExpired();
106
+ await wrapper.vm.$nextTick();
107
+
108
+ // SessionExpiredBanner's "Sign in" is the GoabButton wrapper, which re-exposes
109
+ // goa-button's `_click` as a plain `click` on itself -- but the underlying DOM
110
+ // node vue-test-utils sees is still the raw <goa-button>, so trigger `_click`
111
+ // (same as AppHeader's raw Sign in button above), not `click`.
112
+ const btn = wrapper
113
+ .findAll('goa-button')
114
+ .find((b) => b.text().includes('Sign in') && b.attributes('type') === 'primary');
115
+ await btn?.trigger('_click');
116
+
117
+ expect(kc.keycloak?.login).toHaveBeenCalled();
118
+ expect(useSessionStore(pinia).expired).toBe(false);
119
+ });
120
+ });
@@ -2,26 +2,68 @@
2
2
  import { computed } from 'vue';
3
3
  import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
4
4
  import { RouterView, useRoute } from 'vue-router';
5
- import { AppHeader, AppLayout, AppFooter } from '<%= goaImportPath %>';
5
+ <% if (layout === 'internal') { %>
6
+ import { AppLayout, AppSideMenu, SessionExpiredBanner } from '<%= goaImportPath %>';
7
+ <% } else { %>
8
+ import { AppHeader, AppLayout, AppFooter, SessionExpiredBanner } from '<%= goaImportPath %>';
9
+ <% } %>
10
+ import { useSessionStore } from './stores/session';
6
11
 
7
12
  // Keep the reactive instance — do NOT destructure. useKeycloak() returns a
8
13
  // readonly(reactive()); destructuring snapshots each field at setup time, so
9
14
  // `keycloak` would be frozen at its initial `undefined` (login() a no-op) and
10
15
  // `authenticated` would never flip. Access fields on `kc` to stay reactive.
11
16
  const kc = useKeycloak();
17
+ const session = useSessionStore();
12
18
 
13
19
  // Content width per view, from route meta (default 'page'). Views opt into a
14
20
  // different width with `meta: { layout: 'wide' | 'form' }` in the router.
15
21
  const route = useRoute();
16
- const layout = computed(
22
+ const contentWidth = computed(
17
23
  () => (route.meta.layout as 'page' | 'wide' | 'form' | undefined) ?? 'page'
18
24
  );
19
25
 
20
26
  function login() { kc.keycloak?.login(); }
21
27
  function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin }); }
28
+
29
+ function signInAgain() {
30
+ session.dismiss();
31
+ kc.keycloak?.login();
32
+ }
33
+ <% if (layout === 'internal') { %>
34
+
35
+ // AppSideMenu's account items are presentational -- it doesn't know about
36
+ // Keycloak, it just renders whatever's passed and emits itemClick on click.
37
+ const accountItems = computed(() => [
38
+ kc.authenticated
39
+ ? { label: kc.fullName ? `Sign out (${kc.fullName})` : 'Sign out' }
40
+ : { label: 'Sign in' },
41
+ ]);
42
+
43
+ function onAccountItemClick() {
44
+ if (kc.authenticated) logout();
45
+ else login();
46
+ }
47
+ <% } %>
22
48
  </script>
23
49
 
24
50
  <template>
51
+ <% if (layout === 'internal') { %>
52
+ <AppSideMenu
53
+ heading="<%= projectName %>"
54
+ :account-items="accountItems"
55
+ @item-click="onAccountItemClick"
56
+ >
57
+ <SessionExpiredBanner
58
+ v-model:show="session.expired"
59
+ @sign-in="signInAgain"
60
+ @dismiss="session.dismiss"
61
+ />
62
+ <AppLayout :variant="contentWidth">
63
+ <RouterView />
64
+ </AppLayout>
65
+ </AppSideMenu>
66
+ <% } else { %>
25
67
  <AppHeader heading="<%= projectName %>">
26
68
  <template #utilities>
27
69
  <goa-button-group alignment="end">
@@ -38,11 +80,17 @@ function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin })
38
80
  </goa-button-group>
39
81
  </template>
40
82
  </AppHeader>
83
+ <SessionExpiredBanner
84
+ v-model:show="session.expired"
85
+ @sign-in="signInAgain"
86
+ @dismiss="session.dismiss"
87
+ />
41
88
  <goa-hero-banner heading="<%= projectName %>" backgroundurl="/assets/banner.jpg" />
42
- <AppLayout :variant="layout">
89
+ <AppLayout :variant="contentWidth">
43
90
  <RouterView />
44
91
  </AppLayout>
45
92
  <AppFooter />
93
+ <% } %>
46
94
  </template>
47
95
 
48
96
  <style>
@@ -12,10 +12,12 @@ import keycloakPlugin from '@dsb-norge/vue-keycloak-js';
12
12
  import App from './App.vue';
13
13
  import router from './router';
14
14
  import { environment } from './environments/environment';
15
+ import { useSessionStore } from './stores/session';
15
16
 
16
17
  const app = createApp(App);
18
+ const pinia = createPinia();
17
19
 
18
- app.use(createPinia());
20
+ app.use(pinia);
19
21
  app.use(router);
20
22
  app.use(keycloakPlugin, {
21
23
  config: {
@@ -41,6 +43,13 @@ app.use(keycloakPlugin, {
41
43
  pkceMethod: 'S256',
42
44
  checkLoginIframe: false,
43
45
  },
46
+ // Not onAuthLogout: keycloak-js only invokes that via the session-status
47
+ // iframe (disabled above by checkLoginIframe: false) or in Cordova mode --
48
+ // it would never fire here. The wrapper's own background auto-refresh
49
+ // (autoRefreshToken, on by default) periodically calls updateToken();
50
+ // onAuthRefreshError fires when that fails because the refresh token itself
51
+ // has expired, which is the real, always-on "session expired" signal.
52
+ onAuthRefreshError: () => useSessionStore(pinia).markExpired(),
44
53
  });
45
54
 
46
55
  app.mount('#app');
@@ -0,0 +1,16 @@
1
+ import { defineStore } from 'pinia';
2
+
3
+ // Tracks whether the user's session has expired, surfaced via
4
+ // SessionExpiredBanner in App.vue. Set from main.ts's onAuthRefreshError hook
5
+ // -- see that file for why that hook, not onAuthLogout, is the real signal.
6
+ export const useSessionStore = defineStore('session', {
7
+ state: () => ({ expired: false }),
8
+ actions: {
9
+ markExpired() {
10
+ this.expired = true;
11
+ },
12
+ dismiss() {
13
+ this.expired = false;
14
+ },
15
+ },
16
+ });
@@ -13,6 +13,8 @@ export interface Schema {
13
13
  skipAgent?: boolean;
14
14
  /** Name of an existing backend service project to derive the nginx/dev-server proxy and the adsp:proxy-service: sandbox tag from. */
15
15
  pairedProject?: string;
16
+ /** Top-level app shell: 'header' (default, public-facing) or 'internal' (staff-facing side menu, no header/banner/footer). */
17
+ layout?: 'header' | 'internal';
16
18
  }
17
19
 
18
20
  export interface NormalizedSchema extends Schema {
@@ -83,6 +83,12 @@
83
83
  "type": "boolean",
84
84
  "description": "Skip the consultAgent interaction and generate base scaffolding only.",
85
85
  "default": false
86
+ },
87
+ "layout": {
88
+ "type": "string",
89
+ "description": "Top-level app shell: 'header' is a goa-app-header + hero banner + footer (public-facing). 'internal' is a goa-work-side-menu shell with no header/banner/footer (staff-facing). Both wrap views in the same AppLayout content gutter. Pair two vue-app runs against the same --pairedProject (once each) for a public+internal frontend pairing over one backend.",
90
+ "enum": ["header", "internal"],
91
+ "default": "header"
86
92
  }
87
93
  },
88
94
  "required": ["name", "env"],
@@ -14,6 +14,7 @@ const paired_project_1 = require("../../utils/paired-project");
14
14
  const vue_components_1 = require("../vue-components/vue-components");
15
15
  function normalizeOptions(host, options) {
16
16
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
17
+ var _a;
17
18
  const projectName = (0, devkit_1.names)(options.name).fileName;
18
19
  const projectRoot = `${(0, devkit_1.getWorkspaceLayout)(host).appsDir}/${projectName}`;
19
20
  const openshiftDirectory = `.openshift/${projectName}`;
@@ -36,7 +37,7 @@ function normalizeOptions(host, options) {
36
37
  projectRoot,
37
38
  openshiftDirectory,
38
39
  adsp,
39
- nginxProxies, pairedProjectTag: paired === null || paired === void 0 ? void 0 : paired.tag });
40
+ nginxProxies, pairedProjectTag: paired === null || paired === void 0 ? void 0 : paired.tag, layout: (_a = options.layout) !== null && _a !== void 0 ? _a : 'header' });
40
41
  });
41
42
  }
42
43
  function addFiles(host, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":";;AAuHA,4BA+MC;;AAtUD,wCAIsB;AACtB,6CAA6E;AAC7E,+DAIoC;AACpC,+DAA4D;AAC5D,iDAK6B;AAC7B,uCAAyC;AACzC,uCAYoB;AACpB,6BAA6B;AAC7B,+DAAuE;AACvE,qEAE0C;AAG1C,SAAe,gBAAgB,CAC7B,IAAU,EACV,OAAe;;;QAEf,MAAM,WAAW,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;QACjD,MAAM,WAAW,GAAG,GAAG,IAAA,2BAAkB,EAAC,IAAI,CAAC,CAAC,OAAO,IAAI,WAAW,EAAE,CAAC;QACzE,MAAM,kBAAkB,GAAG,cAAc,WAAW,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;YAClD,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;YACpB,CAAC,CAAC,OAAO,CAAC,KAAK;gBACb,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;gBACjB,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,MAAM,GAAG,IAAA,0CAAyB,EAAC,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QACtE,IACE,MAAM;YACN,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EACjE,CAAC;YACD,MAAM,IAAI,KAAK,CACb,gDAAgD,MAAM,CAAC,KAAK,CAAC,QAAQ,iBAAiB;gBACpF,4EAA4E,CAC/E,CAAC;QACJ,CAAC;QACD,MAAM,YAAY,GAAG,MAAM;YACzB,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,eAAe,CAAC;YACpC,CAAC,CAAC,eAAe,CAAC;QACpB,uCACK,OAAO,KACV,WAAW;YACX,WAAW;YACX,kBAAkB;YAClB,IAAI;YACJ,YAAY,EACZ,gBAAgB,EAAE,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,GAAG,EAC7B,MAAM,EAAE,MAAA,OAAO,CAAC,MAAM,mCAAI,QAAQ,IAClC;IACJ,CAAC;CAAA;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,OAAyB;;IACrD,MAAM,eAAe,iDAChB,OAAO,GACP,OAAO,CAAC,IAAI,KACf,cAAc,EAAE,IAAA,uBAAc,EAAC,OAAO,CAAC,WAAW,CAAC,EACnD,aAAa,EAAE,MAAA,OAAO,CAAC,aAAa,mCAAI,IAAI;QAC5C,6EAA6E;QAC7E,aAAa,EAAE,IAAA,wCAAuB,EAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,EAAE,GACT,CAAC;IACF,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,WAAW,EACnB,eAAe,CAChB,CAAC;IAEF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IACrD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAC9C,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE;YACxB,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG;gBACZ,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,cAAc,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC7F,MAAM,EAAE,WAAW,CAAC,QAAQ,KAAK,QAAQ;gBACzC,YAAY,EAAE,KAAK;gBACnB,WAAW,EAAE,EAAE;aAChB,CAAC;YACF,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpC,KAAK,CAAC,WAAW,GAAG;oBAClB,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ;iBAClD,CAAC;YACJ,CAAC;YACD,uCAAY,SAAS,KAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAG;QACxD,CAAC,EACD,EAAE,CACH,CAAC;QACF,IAAA,kBAAS,EAAC,IAAI,EAAE,GAAG,OAAO,CAAC,WAAW,kBAAkB,EAAE,YAAY,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;;QACxD,4EAA4E;QAC5E,2EAA2E;QAC3E,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,GAAG,MAAM,qCAAO,SAAS,GAAE,KAAK,CACrE,GAAG,EAAE;YACH,MAAM,IAAI,KAAK,CACb,mGAAmG,CACpG,CAAC;QACJ,CAAC,CACF,CAAC;QAEF,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEhE,MAAM,OAAO,CAAC,IAAI,EAAE;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,QAAQ;YACxB,aAAa,EAAE,YAAY;YAC3B,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,iBAAiB,CAAC,WAAW;SACzC,CAAC,CAAC;QAEH,6EAA6E;QAC7E,0EAA0E;QAC1E,IAAA,kCAAwB,EAAC,IAAI,EAAE,GAAG,iBAAiB,CAAC,WAAW,MAAM,CAAC,CAAC;QACvE,IAAA,kCAAwB,EAAC,IAAI,EAAE,GAAG,iBAAiB,CAAC,WAAW,MAAM,CAAC,CAAC;QAEvE,8EAA8E;QAC9E,+EAA+E;QAC/E,wEAAwE;QACxE,6EAA6E;QAC7E,+EAA+E;QAC/E,6EAA6E;QAC7E,mCAAmC;QACnC,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAA,qCAA4B,EAC1B,IAAI,EACJ;YACE,sBAAsB,EAAE,QAAQ;YAChC,uBAAuB,EAAE,OAAO;YAChC,wEAAwE;YACxE,iEAAiE;YACjE,4BAA4B,EAAE,QAAQ;YACtC,KAAK,EAAE,QAAQ;YACf,YAAY,EAAE,QAAQ;SACvB,EACD;YACE,sBAAsB,EAAE,SAAS;YACjC,wBAAwB,EAAE,QAAQ;YAClC,0BAA0B,EAAE,QAAQ;SACrC,CACF,CAAC;QAEF,gFAAgF;QAChF,0EAA0E;QAC1E,0EAA0E;QAC1E,4EAA4E;QAC5E,wEAAwE;QACxE,4EAA4E;QAC5E,KAAK,MAAM,CAAC,IAAI;YACd,aAAa;YACb,iBAAiB;YACjB,qBAAqB;YACrB,uBAAuB;YACvB,+BAA+B;YAC/B,qEAAqE;YACrE,+DAA+D;YAC/D,8BAA8B;YAC9B,yBAAyB;YACzB,iBAAiB;SAClB,EAAE,CAAC;YACF,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC,WAAW,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBACzD,IAAI,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC,WAAW,IAAI,CAAC,EAAE,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAErD,IAAA,+BAAqB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC3D,2EAA2E;QAC3E,yEAAyE;QACzE,kBAAkB;QAClB,MAAM,IAAA,cAAa,EAAC,IAAI,CAAC,CAAC;QAE1B,MAAM,MAAM,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAE5D,4EAA4E;QAC5E,IAAI,UAAU,KAAI,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,0CAAE,OAAO,CAAA,EAAE,CAAC;YAChD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,mCACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,KAC/B,WAAW,EAAE,GAAG,iBAAiB,CAAC,WAAW,kBAAkB,GAChE,CAAC;QACJ,CAAC;QAED,yCAAyC;QACzC,wEAAwE;QACxE,4EAA4E;QAC5E,yEAAyE;QACzE,oCAAoC;QACpC,IAAI,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,0CAAE,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,mCACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,KAC/B,UAAU,EAAE,QAAQ,iBAAiB,CAAC,WAAW,EAAE,GACpD,CAAC;QACJ,CAAC;QAED,oFAAoF;QACpF,iEAAiE;QACjE,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAA,MAAM,CAAC,IAAI,mCAAI,EAAE,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,CAAC,IAAI,GAAG;YACZ,GAAG,CAAC,MAAA,MAAM,CAAC,IAAI,mCAAI,EAAE,CAAC;YACtB,GAAG,IAAA,uBAAe,EAChB,iBAAiB,CAAC,GAAG,EACrB,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAC9B,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,WAAC,OAAA,CAAC,CAAC,MAAA,MAAM,CAAC,IAAI,mCAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA,EAAA,CAAC;SACtD,CAAC;QAEF,IAAA,mCAA0B,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAEvD,IAAA,0BAAgB,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,IAAI,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC3B,MAAM,WAAW,GACf,MAAA,iBAAiB,CAAC,IAAI,CAAC,WAAW,mCAAI,OAAO,CAAC,WAAW,CAAC;YAC5D,MAAM,QAAQ,GAAG,WAAW,iBAAiB,CAAC,IAAI,CAAC,MAAM,IAAI,iBAAiB,CAAC,WAAW,EAAE,CAAC;YAC7F,MAAM,IAAA,mCAAkB,EACtB,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,WAAW,CACZ,CAAC;YACF,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;gBAC5B,MAAM,IAAA,qCAAoB,EACxB,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,OAAO,CAAC,eAAe,EACvB,WAAW,CACZ,CAAC;gBACF,MAAM,IAAA,sCAAqB,EACzB,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,OAAO,CAAC,eAAe,EACvB,cAAc,EACd,WAAW,CACZ,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,iBAAiB,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YACjD,MAAM,WAAW,GACf,MAAA,iBAAiB,CAAC,IAAI,CAAC,WAAW,mCAAI,OAAO,CAAC,WAAW,CAAC;YAC5D,MAAM,MAAM,GACV,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,cAAc,CAAC,0CAAE,QAAQ,EAAE,mCACrE,EAAE,CAAC;YACL,MAAM,MAAM,GACV,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,cAAc,CAAC,0CAAE,QAAQ,EAAE,mCACrE,EAAE,CAAC;YACL,MAAM,QAAQ,GACZ,MAAA,MAAA,IAAI;iBACD,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,sBAAsB,CAAC,0CAC3D,QAAQ,EAAE,mCAAI,EAAE,CAAC;YACvB,MAAM,aAAa,GACjB,MAAA,MAAA,IAAI;iBACD,IAAI,CACH,GAAG,iBAAiB,CAAC,WAAW,kCAAkC,CACnE,0CACC,QAAQ,EAAE,mCAAI,EAAE,CAAC;YACvB,MAAM,IAAA,kCAA0B,EAC9B,MAAM,IAAA,oBAAY,EAChB,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,EAC1C,WAAW,EACX;gBACE,WAAW,EAAE,iBAAiB,CAAC,WAAW;gBAC1C,WAAW,EAAE,SAAS;gBACtB,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,MAAM;gBACrC,aAAa,EAAE,+BAAc;gBAC7B,aAAa,EAAE;oBACb,aAAa,EAAE,MAAM;oBACrB,aAAa,EAAE,MAAM;oBACrB,qBAAqB,EAAE,QAAQ;oBAC/B,iCAAiC,EAAE,aAAa;iBACjD;aACF,EACD,IAAI,EACJ,iBAAiB,CAAC,WAAW,CAC9B,CACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAA,2BAAmB,EAAC,IAAI,kCACzB,iBAAiB,KACpB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,iBAAiB,CAAC,WAAW,IACtC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA"}
@@ -172,17 +172,77 @@ describe('Vue App Generator', () => {
172
172
  'AppLayout',
173
173
  'AppHeader',
174
174
  'AppFooter',
175
+ 'AppSideMenu',
175
176
  'SessionExpiredBanner',
176
177
  ]) {
177
178
  expect(host.exists(`${patterns}/${name}.vue`)).toBeTruthy();
178
179
  }
179
180
 
180
181
  const app = host.read('apps/test/src/App.vue').toString();
181
- expect(app).toContain("import { AppHeader, AppLayout, AppFooter } from '@proj/vue-components';");
182
+ expect(app).toContain(
183
+ "import { AppHeader, AppLayout, AppFooter, SessionExpiredBanner } from '@proj/vue-components';",
184
+ );
182
185
  expect(app).toContain('<AppHeader heading="test">');
183
186
  expect(app).toContain('<AppFooter />');
184
187
  }, 30000);
185
188
 
189
+ it('--layout=internal generates an AppSideMenu shell instead of AppHeader/AppFooter', async () => {
190
+ await generator(host, { ...options, layout: 'internal' });
191
+
192
+ const app = host.read('apps/test/src/App.vue').toString();
193
+ expect(app).toContain(
194
+ "import { AppLayout, AppSideMenu, SessionExpiredBanner } from '@proj/vue-components';",
195
+ );
196
+ expect(app).not.toContain('AppHeader');
197
+ expect(app).not.toContain('AppFooter');
198
+ expect(app).not.toContain('goa-hero-banner');
199
+ expect(app).toContain('<AppSideMenu');
200
+ expect(app).toContain('heading="test"');
201
+ expect(app).toContain(':account-items="accountItems"');
202
+ expect(app).toContain('@item-click="onAccountItemClick"');
203
+ // The content gutter is shared regardless of shell choice.
204
+ expect(app).toContain('<AppLayout');
205
+
206
+ const agents = host.read('apps/test/AGENTS.md').toString();
207
+ expect(agents).toContain('--layout=internal');
208
+ expect(agents).toContain('AppSideMenu');
209
+ }, 30000);
210
+
211
+ it('defaults --layout to header when omitted', async () => {
212
+ await generator(host, options);
213
+ const app = host.read('apps/test/src/App.vue').toString();
214
+ expect(app).toContain('AppHeader');
215
+ expect(app).not.toContain('AppSideMenu');
216
+ }, 30000);
217
+
218
+ it('wires SessionExpiredBanner to a session store fed by onAuthRefreshError (not onAuthLogout)', async () => {
219
+ await generator(host, options);
220
+
221
+ expect(host.exists('apps/test/src/stores/session.ts')).toBeTruthy();
222
+ const store = host.read('apps/test/src/stores/session.ts').toString();
223
+ expect(store).toContain("defineStore('session'");
224
+ expect(store).toContain('markExpired');
225
+
226
+ // Strip // comments — main.ts legitimately explains, in prose, why
227
+ // onAuthLogout is the wrong hook; assert against the actual init code.
228
+ const mainTsCode = host
229
+ .read('apps/test/src/main.ts')
230
+ .toString()
231
+ .split('\n')
232
+ .filter((l) => !l.trim().startsWith('//'))
233
+ .join('\n');
234
+ expect(mainTsCode).toContain('onAuthRefreshError');
235
+ // Regression guard: onAuthLogout only fires via the session-status iframe
236
+ // (disabled here) or Cordova mode — it would never fire in this app.
237
+ expect(mainTsCode).not.toContain('onAuthLogout');
238
+ expect(mainTsCode).toContain('useSessionStore(pinia).markExpired()');
239
+
240
+ const app = host.read('apps/test/src/App.vue').toString();
241
+ expect(app).toContain('v-model:show="session.expired"');
242
+ expect(app).toContain('@sign-in="signInAgain"');
243
+ expect(app).toContain('@dismiss="session.dismiss"');
244
+ }, 30000);
245
+
186
246
  it('inits Keycloak with no hidden iframes so init never hangs', async () => {
187
247
  await generator(host, options);
188
248
  // Strip // comments — they legitimately reference the disabled iframe options
@@ -7,7 +7,7 @@ app in this workspace imports both instead of carrying its own copy. Generated b
7
7
  | Folder | Contains | Lifespan |
8
8
  |---|---|---|
9
9
  | `src/lib/primitives/` | Thin `v-model`/idiomatic-event wrappers over individual `goa-*` elements (`GoabInput`, `GoabButton`, …) | **Interim** — see below |
10
- | `src/lib/patterns/` | Composite, app-shell components (`AppLayout`, `AppHeader`, `AppFooter`, `SessionExpiredBanner`) | **Permanent** |
10
+ | `src/lib/patterns/` | Composite, app-shell components (`AppLayout`, `AppHeader`, `AppFooter`, `AppSideMenu`, `SessionExpiredBanner`) | **Permanent** |
11
11
 
12
12
  > **⚠️ `primitives/` is interim — do not invest in it as permanent.** It exists
13
13
  > only because GoA DS has not yet published an official Vue wrapper package. When
@@ -142,7 +142,7 @@ detail); just leave it to fall through from the caller.
142
142
 
143
143
  A pattern component is app-shell composition — layout, header/footer chrome,
144
144
  banners — not a single-element wrapper. Existing examples: `AppLayout`,
145
- `AppHeader`, `AppFooter`, `SessionExpiredBanner`.
145
+ `AppHeader`, `AppFooter`, `AppSideMenu`, `SessionExpiredBanner`.
146
146
 
147
147
  - It's fine to compose `primitives/` wrappers inside a pattern component (e.g.
148
148
  `SessionExpiredBanner` uses `GoabButton`) — import them with a relative path
@@ -20,4 +20,5 @@ export { default as GoabModal } from './lib/primitives/GoabModal.vue';
20
20
  export { default as AppLayout } from './lib/patterns/AppLayout.vue';
21
21
  export { default as AppHeader } from './lib/patterns/AppHeader.vue';
22
22
  export { default as AppFooter } from './lib/patterns/AppFooter.vue';
23
+ export { default as AppSideMenu } from './lib/patterns/AppSideMenu.vue';
23
24
  export { default as SessionExpiredBanner } from './lib/patterns/SessionExpiredBanner.vue';
@@ -0,0 +1,96 @@
1
+ <script setup lang="ts">
2
+ // The alternate top-level shell for staff-facing/internal apps (goa-work-side-menu),
3
+ // used instead of AppHeader/AppFooter -- not together with them. Presentational only:
4
+ // unlike the real GovAlta-Pronghorn source this is adapted from, active-route
5
+ // highlighting and navigation are the caller's job (pass `current` on each item,
6
+ // listen for `item-click`) so this component doesn't need vue-router as a dependency.
7
+ interface AppSideMenuItem {
8
+ label: string;
9
+ to?: string;
10
+ icon?: string;
11
+ badge?: string;
12
+ current?: boolean;
13
+ }
14
+
15
+ withDefaults(
16
+ defineProps<{
17
+ heading?: string;
18
+ userName?: string;
19
+ primaryItems?: AppSideMenuItem[];
20
+ secondaryItems?: AppSideMenuItem[];
21
+ accountItems?: AppSideMenuItem[];
22
+ }>(),
23
+ {
24
+ primaryItems: () => [],
25
+ secondaryItems: () => [],
26
+ accountItems: () => [],
27
+ },
28
+ );
29
+
30
+ const emit = defineEmits<{ itemClick: [item: AppSideMenuItem] }>();
31
+ </script>
32
+
33
+ <template>
34
+ <div class="app-side-menu">
35
+ <a class="skip-link" href="#main-content">Skip to main content</a>
36
+ <goa-work-side-menu :heading="heading" :user-name="userName">
37
+ <goa-work-side-menu-item
38
+ v-for="item in primaryItems"
39
+ :key="item.label"
40
+ slot="primary"
41
+ :icon="item.icon"
42
+ :label="item.label"
43
+ :url="item.to"
44
+ :current="item.current"
45
+ @click.prevent="emit('itemClick', item)"
46
+ />
47
+ <goa-work-side-menu-item
48
+ v-for="item in secondaryItems"
49
+ :key="item.label"
50
+ slot="secondary"
51
+ :icon="item.icon"
52
+ :label="item.label"
53
+ :badge="item.badge"
54
+ @click.prevent="emit('itemClick', item)"
55
+ />
56
+ <goa-work-side-menu-item
57
+ v-for="item in accountItems"
58
+ :key="item.label"
59
+ slot="account"
60
+ :label="item.label"
61
+ :url="item.to"
62
+ @click.prevent="emit('itemClick', item)"
63
+ />
64
+ </goa-work-side-menu>
65
+ <main id="main-content" class="app-side-menu-content">
66
+ <slot />
67
+ </main>
68
+ </div>
69
+ </template>
70
+
71
+ <style scoped>
72
+ .app-side-menu {
73
+ min-height: 100vh;
74
+ display: flex;
75
+ }
76
+
77
+ .app-side-menu-content {
78
+ flex: 1;
79
+ min-width: 0;
80
+ overflow-y: auto;
81
+ }
82
+
83
+ .skip-link {
84
+ position: absolute;
85
+ left: -9999px;
86
+ top: 0;
87
+ z-index: 100;
88
+ padding: var(--goa-space-s, 0.5rem) var(--goa-space-m, 1rem);
89
+ background: var(--goa-color-interactive-default, #0070c4);
90
+ color: #fff;
91
+ }
92
+
93
+ .skip-link:focus {
94
+ left: var(--goa-space-m, 1rem);
95
+ }
96
+ </style>
@@ -1,10 +1,12 @@
1
1
  <script setup lang="ts">
2
- // Presentational only — not yet wired to a real Keycloak session-expiry signal
3
- // by any generator. @dsb-norge/vue-keycloak-js has no reactive "expired" field
4
- // on the useKeycloak() instance; the closest real hook is the `onAuthLogout`
5
- // callback configured at plugin-install time in main.ts. Wiring that up is a
6
- // separate change (new shared reactive state + main.ts plumbing); this
7
- // component just renders the banner once some caller flips `show`.
2
+ // Presentational only — flipping `show` is the consuming app's job, not this
3
+ // component's. vue-app wires it to its session store's `expired` flag, itself
4
+ // set from main.ts's Keycloak plugin `onAuthRefreshError` hook NOT
5
+ // `onAuthLogout`, which keycloak-js only fires via the session-status iframe
6
+ // (typically disabled, e.g. via checkLoginIframe: false) or Cordova mode, so
7
+ // it would never fire in a normal web deployment. onAuthRefreshError fires
8
+ // from the wrapper's own background auto-refresh failing because the refresh
9
+ // token itself expired — the real, always-on signal.
8
10
  import GoabButton from '../primitives/GoabButton.vue';
9
11
 
10
12
  const show = defineModel<boolean>('show', { default: false });
@@ -24,6 +24,7 @@ describe('vue-components', () => {
24
24
  'AppLayout',
25
25
  'AppHeader',
26
26
  'AppFooter',
27
+ 'AppSideMenu',
27
28
  'SessionExpiredBanner',
28
29
  ]) {
29
30
  expect(lib[name as keyof typeof lib]).toBeTruthy();
@@ -41,6 +41,7 @@ describe('Vue Components Generator', () => {
41
41
  'AppLayout',
42
42
  'AppHeader',
43
43
  'AppFooter',
44
+ 'AppSideMenu',
44
45
  'SessionExpiredBanner',
45
46
  ]) {
46
47
  expect(host.exists(`${patterns}/${name}.vue`)).toBeTruthy();