Files
agent-frontend-web/docs/superpowers/specs/2026-08-04-hide-chrome-for-common-role-design.md
2026-08-07 13:56:29 +08:00

106 lines
4.7 KiB
Markdown

# Hide Layout Chrome for `common` Role Users
Date: 2026-08-04
Status: Approved (design phase)
## Goal
When the logged-in user's role list contains `"common"`, hide all layout chrome — left sidebar, top navbar, tags view, and the floating Settings button — leaving only `<app-main />` filling the viewport. Non-`common` users see the existing full layout unchanged.
## Trigger Condition
`useUserStore().roles` (array) includes the string `"common"`.
The check is `roles.includes('common')`. A user with `['admin', 'common']` is treated as `common` because the array includes it; only roles where `common` is present trigger the new behavior.
## Scope
- Applies to **all routes**, unconditionally.
- No per-route opt-out, no per-route opt-in.
- Only the layout chrome is affected. `<app-main />` keeps rendering any page contents exactly as before.
## Files Touched
Only one file: `src/layout/index.vue`.
No stores, no router, no permission utilities, no new files.
## Design
### 1. Add a `isCommonUser` computed in `src/layout/index.vue`
```js
import useUserStore from '@/store/modules/user'
const isCommonUser = computed(() =>
useUserStore().roles?.includes('common') ?? false
)
```
Defensive `?.includes` so an undefined `roles` collapses to `false` rather than throwing.
### 2. Gate the four chrome elements with `v-if`
```vue
<sidebar v-if="!sidebar.hide && !isCommonUser" class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide, fullscreen: isCommonUser }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar v-if="!isCommonUser" @setLayout="setLayout" />
<tags-view v-if="needTagsView && !isCommonUser" />
</div>
<app-main />
<settings v-if="!isCommonUser" ref="settingRef" />
</div>
```
Notes:
- `Settings` is hidden even though the original requirement listed only sidebar / navbar / tagsview. The user explicitly confirmed in brainstorming that Settings should also be hidden for cleanliness.
- The outer `<div :class="classObj">` (with `hideSidebar` / `openSidebar` / `mobile`) is left untouched; it still affects the `<sidebar>` slot even when sidebar is hidden, which is harmless.
### 3. Fullscreen styling for `<app-main />`
Add a scoped SCSS rule:
```scss
.main-container.fullscreen {
position: absolute;
inset: 0;
width: 100% !important;
height: 100% !important;
margin-left: 0 !important;
}
```
This overrides any inherited width / margin-left from the sidebar-open state so `<app-main />` truly fills the viewport.
`<app-main />` internals (page padding, scoped styles) are **not** touched. If a future page wants a chrome-less padding experience, it owns that decision.
## Out of Scope
- Not changing user store APIs.
- Not adding a getter to `useUserStore` (single use site — YAGNI).
- Not introducing a second layout component or per-route layout switching.
- Not modifying router meta or permission middleware.
- Not changing `<app-main />` internals or any page component.
## Error / Edge Cases
- `roles` is `undefined` or `null``?.includes('common')` returns `undefined`, `?? false` collapses to `false`. Safe.
- `roles` is a non-array → `Array.prototype.includes` would throw. The user store initializes `roles: []`, so this is only a risk if a future change sets it to a non-array. No mitigation needed; treat as a contract violation.
- User role changes mid-session (does not happen in this app) → computed reacts automatically, layout re-renders. Reactive by design.
- Page refresh → `useUserStore` is repopulated via `getInfo()` during router guard; the computed is reactive and picks up the new value on next render. No flicker handling needed because the layout renders after auth is resolved.
## Testing
The repo has no frontend unit / e2e tests configured (`package.json` scripts only cover `dev` / `build`). Verification is manual:
1. **Admin role**: log in as a user without `common` in roles. All four chrome areas render as before.
2. **Common role**: log in as a user with `common` in roles. Only `<app-main />` is visible, filling the viewport edge-to-edge.
3. **Negative variant**: log in as a user with `['common', 'other-role']` — chrome still hidden (array `.includes` semantics).
4. **Page interaction**: inside `<app-main />`, the page's own links / buttons / forms work normally.
## Future Considerations (not implemented)
- If a second role-driven layout variant appears (e.g. compact admin view), lift `isCommonUser` into a `useUserStore` getter and consider per-route layout meta.
- If pages ever need to opt out of fullscreen (e.g. a "preview" page that wants the chrome visible for a `common` user), introduce per-route meta and gate on it. Not a current requirement.