Task #613: replace per-app hide toggles with single Dock visibility switch

The per-app hidden feature shipped in #609 was rejected — user wanted one
toggle that hides/shows the entire bottom AppDock bar, leaving Home apps
untouched.

Removed:
- lib/db/src/schema/user-hidden-apps.ts (and index.ts export); table
  dropped via `pnpm --filter @workspace/db run push`
- GET /me/apps and PUT /me/apps/:appId/hidden routes
- getHiddenAppIdsForUser + getVisibleNonHiddenAppsForUser helpers
- MyApp + UpdateMyAppHiddenBody OpenAPI schemas
- MyAppsBody settings UI + related i18n keys
- Regenerated api-client-react + api-zod from the trimmed spec
- Reverted GET /apps back to getVisibleAppsForUser

Added:
- artifacts/tx-os/src/hooks/use-dock-visible.ts — localStorage-backed
  preference with a custom window event for in-tab sync and the native
  `storage` event for cross-tab sync. Default = true.
- DockBody in settings-panel: single ToggleRow under a new "App dock"
  section ("settingsPanel.section.dock" / "settingsPanel.dock.show")
- AppDock now returns null when the preference is off and clears
  `--app-dock-height` so page padding doesn't stay reserved.

Code review: PASS (architect). No remaining references to the removed
infra. Typecheck shows only pre-existing errors in executive-meetings.ts
and push.ts unrelated to this change.

Follow-up: publish to Gitea + redeploy on Mac (proposed as a follow-up
task).
This commit is contained in:
riyadhafraa
2026-05-19 11:35:13 +00:00
parent f1536cb4db
commit 3f6a0fb02f
13 changed files with 95 additions and 638 deletions
-145
View File
@@ -994,151 +994,6 @@ export const UpdateMyAppOrderResponseItem = zod.object({
});
export const UpdateMyAppOrderResponse = zod.array(UpdateMyAppOrderResponseItem);
/**
* Returns every globally active app (regardless of the per-user hidden
flag), each with a `hidden` boolean indicating whether the current
user has hidden it from their own Home/Dock via PUT /me/apps/{appId}/hidden.
Used by the per-user Settings panel; the Home and Dock continue to use
GET /apps which already filters out hidden apps server-side.
* @summary List apps for the current user with their per-user hidden state
*/
export const ListMyAppsResponseItem = zod
.object({
id: zod.number(),
slug: zod.string(),
nameAr: zod.string(),
nameEn: zod.string(),
descriptionAr: zod.string().nullish(),
descriptionEn: zod.string().nullish(),
iconName: zod.string(),
imageUrl: zod
.string()
.nullish()
.describe(
'Optional uploaded image used in place of the Lucide icon on the\nlauncher tile. Stored as the object-storage path returned by the\nupload presigner (e.g. \"\/objects\/<id>\").\n',
),
route: zod.string(),
externalUrl: zod
.string()
.nullish()
.describe(
'For openMode \"external_tab\" or \"external_iframe\": the URL the\nlauncher should open. Ignored when openMode is \"internal\".\n',
),
openMode: zod
.enum(["internal", "external_tab", "external_iframe"])
.describe(
'How the launcher opens this app: \"internal\" navigates to `route`\ninside the SPA; \"external_tab\" opens externalUrl in a new tab;\n\"external_iframe\" navigates to \/embedded\/:id, which renders\nexternalUrl inside an iframe shell.\n',
),
color: zod.string(),
isActive: zod.boolean(),
isSystem: zod.boolean(),
sortOrder: zod.number(),
createdAt: zod.coerce.date(),
updatedAt: zod.coerce.date(),
groupCount: zod
.number()
.optional()
.describe(
"Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
),
restrictionCount: zod
.number()
.optional()
.describe(
"Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
),
openCount: zod
.number()
.optional()
.describe(
"Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
),
})
.and(
zod.object({
hidden: zod
.boolean()
.describe(
"True when the current user has hidden this app from their Home\/Dock.",
),
}),
);
export const ListMyAppsResponse = zod.array(ListMyAppsResponseItem);
/**
* @summary Hide or show an app for the current user
*/
export const UpdateMyAppHiddenParams = zod.object({
appId: zod.coerce.number(),
});
export const UpdateMyAppHiddenBody = zod.object({
hidden: zod.boolean(),
});
export const UpdateMyAppHiddenResponse = zod
.object({
id: zod.number(),
slug: zod.string(),
nameAr: zod.string(),
nameEn: zod.string(),
descriptionAr: zod.string().nullish(),
descriptionEn: zod.string().nullish(),
iconName: zod.string(),
imageUrl: zod
.string()
.nullish()
.describe(
'Optional uploaded image used in place of the Lucide icon on the\nlauncher tile. Stored as the object-storage path returned by the\nupload presigner (e.g. \"\/objects\/<id>\").\n',
),
route: zod.string(),
externalUrl: zod
.string()
.nullish()
.describe(
'For openMode \"external_tab\" or \"external_iframe\": the URL the\nlauncher should open. Ignored when openMode is \"internal\".\n',
),
openMode: zod
.enum(["internal", "external_tab", "external_iframe"])
.describe(
'How the launcher opens this app: \"internal\" navigates to `route`\ninside the SPA; \"external_tab\" opens externalUrl in a new tab;\n\"external_iframe\" navigates to \/embedded\/:id, which renders\nexternalUrl inside an iframe shell.\n',
),
color: zod.string(),
isActive: zod.boolean(),
isSystem: zod.boolean(),
sortOrder: zod.number(),
createdAt: zod.coerce.date(),
updatedAt: zod.coerce.date(),
groupCount: zod
.number()
.optional()
.describe(
"Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
),
restrictionCount: zod
.number()
.optional()
.describe(
"Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
),
openCount: zod
.number()
.optional()
.describe(
"Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
),
})
.and(
zod.object({
hidden: zod
.boolean()
.describe(
"True when the current user has hidden this app from their Home\/Dock.",
),
}),
);
/**
* @summary Get application settings (public)
*/