- New `user_hidden_apps` table (userId+appId composite PK, cascade) in
lib/db/src/schema/user-hidden-apps.ts; registered in schema index;
pushed to dev DB via drizzle-kit.
- Backend (artifacts/api-server/src/routes/apps.ts):
- GET /me/apps — returns every globally-active app visible to the
user with a `hidden` flag.
- PUT /me/apps/:appId/hidden — toggles a row in user_hidden_apps,
gated by getVisibleAppsForUser + isActive so a user can't toggle
apps they can't reach.
- GET /apps (Home/Dock) now uses getVisibleNonHiddenAppsForUser so
hidden apps disappear immediately.
- lib/appsVisibility.ts: added getHiddenAppIdsForUser and
getVisibleNonHiddenAppsForUser helpers.
- OpenAPI: added /me/apps + /me/apps/{appId}/hidden, MyApp and
UpdateMyAppHiddenBody schemas; regenerated api-zod + api-client-react.
- Frontend (settings-panel.tsx): added "My apps" accordion section as
first GroupItem with MyAppsBody — Switch per app, optimistic update,
invalidates getListMyAppsQueryKey + getListAppsQueryKey so Home/Dock
refresh without reload.
- Translations: added settingsPanel.section.myApps + settingsPanel.myApps
in ar.json + en.json.
- Code review fix: /me/apps and PUT gating filter isActive even for
admins, so inactive apps don't appear in the Settings list.
- Proposed follow-up #611 (Playwright test that hidden apps disappear
from Home and Dock).
- New `user_hidden_apps` table (userId+appId composite PK, cascade) in
lib/db/src/schema/user-hidden-apps.ts; registered in schema index;
pushed to dev DB via drizzle-kit.
- Backend (artifacts/api-server/src/routes/apps.ts):
- GET /me/apps — returns every globally-active app visible to the
user with a `hidden` flag.
- PUT /me/apps/:appId/hidden — toggles a row in user_hidden_apps,
gated by getVisibleAppsForUser + isActive so a user can't toggle
apps they can't reach.
- GET /apps (Home/Dock) now uses getVisibleNonHiddenAppsForUser so
hidden apps disappear immediately.
- lib/appsVisibility.ts: added getHiddenAppIdsForUser and
getVisibleNonHiddenAppsForUser helpers.
- OpenAPI: added /me/apps + /me/apps/{appId}/hidden, MyApp and
UpdateMyAppHiddenBody schemas; regenerated api-zod + api-client-react.
- Frontend (settings-panel.tsx): added "My apps" accordion section as
first GroupItem with MyAppsBody — Switch per app, optimistic update,
invalidates getListMyAppsQueryKey + getListAppsQueryKey so Home/Dock
refresh without reload.
- Translations: added settingsPanel.section.myApps + settingsPanel.myApps
in ar.json + en.json.
- Code review fix: /me/apps and PUT gating filter isActive even for
admins, so inactive apps don't appear in the Settings list.
- Proposed follow-up #611 (Playwright test that hidden apps disappear
from Home and Dock).
Problem: The floating AppDock (position: fixed at bottom-2/3) overlapped
the last row of long pages on mobile — e.g. meeting #7 on the Meetings
page was hidden behind the dock with no way to scroll it into view.
Fix:
- AppDock measures its rendered outer-bottom extent (height + bottom
offset + safe-area inset) via getBoundingClientRect() and publishes
it as `--app-dock-height` on documentElement, with a small 8px gap
so the last row doesn't kiss the dock.
- Re-measures on ResizeObserver, window resize, and orientationchange
so the value stays correct across rotation, URL-bar collapse, and
dock content changes.
- Clears the variable on unmount and whenever the dock hides
(≤1 other app), so pages without a dock get no extra padding.
- Global rule in index.css: `body { padding-bottom: var(--app-dock-height, 0px) }`,
scoped to `@media not print` so PDF exports stay unaffected.
Files:
- artifacts/tx-os/src/components/app-dock.tsx
- artifacts/tx-os/src/index.css
No deviations from the task plan.
Problem: The floating AppDock (position: fixed at bottom-2/3) overlapped
the last row of long pages on mobile — e.g. meeting #7 on the Meetings
page was hidden behind the dock with no way to scroll it into view.
Fix:
- AppDock measures its rendered outer-bottom extent (height + bottom
offset + safe-area inset) via getBoundingClientRect() and publishes
it as `--app-dock-height` on documentElement, with a small 8px gap
so the last row doesn't kiss the dock.
- Re-measures on ResizeObserver, window resize, and orientationchange
so the value stays correct across rotation, URL-bar collapse, and
dock content changes.
- Clears the variable on unmount and whenever the dock hides
(≤1 other app), so pages without a dock get no extra padding.
- Global rule in index.css: `body { padding-bottom: var(--app-dock-height, 0px) }`,
scoped to `@media not print` so PDF exports stay unaffected.
Files:
- artifacts/tx-os/src/components/app-dock.tsx
- artifacts/tx-os/src/index.css
No deviations from the task plan.
Problem: On phone-width viewports (~375-430px), the floating
FormattingToolbar in EditableCell rendered as a single row with
17+ controls (B/I/U + 7 color swatches + 3 align buttons + font +
size + Save/Cancel). The row was wider than the screen, so users
only saw the edges (X, ✓, two "default" dropdowns) and could not
reach Bold/Italic/Underline, color swatches, alignment, or font
controls. The horizontal clamp also relied on the table's
scroll-container bounds, which on mobile extend beyond the viewport
because the table is wider than the screen — so the clamp could
park the toolbar partially off-screen.
Fix in artifacts/tx-os/src/components/editable-cell.tsx:
- Toolbar container: `flex` → `flex flex-wrap` with `gap-x-1 gap-y-1`
and `max-w-[calc(100vw-16px)]` so it wraps onto multiple rows
whenever a single row would exceed the viewport. Bumped `py` to
`py-1` for breathing room between wrapped rows.
- Horizontal clamp: in addition to the scroll-container bounds,
also clamp against the viewport (`[8, window.innerWidth - tbWidth
- 8]`) so the toolbar always lands fully on-screen even when the
scroll container is wider than the screen.
Scope: visual / positioning only. No changes to TipTap config,
toolbar buttons, or save/cancel logic. Above-vs-below placement
(#581 iOS keyboard handling) is preserved.
Problem: On phone-width viewports (~375-430px), the floating
FormattingToolbar in EditableCell rendered as a single row with
17+ controls (B/I/U + 7 color swatches + 3 align buttons + font +
size + Save/Cancel). The row was wider than the screen, so users
only saw the edges (X, ✓, two "default" dropdowns) and could not
reach Bold/Italic/Underline, color swatches, alignment, or font
controls. The horizontal clamp also relied on the table's
scroll-container bounds, which on mobile extend beyond the viewport
because the table is wider than the screen — so the clamp could
park the toolbar partially off-screen.
Fix in artifacts/tx-os/src/components/editable-cell.tsx:
- Toolbar container: `flex` → `flex flex-wrap` with `gap-x-1 gap-y-1`
and `max-w-[calc(100vw-16px)]` so it wraps onto multiple rows
whenever a single row would exceed the viewport. Bumped `py` to
`py-1` for breathing room between wrapped rows.
- Horizontal clamp: in addition to the scroll-container bounds,
also clamp against the viewport (`[8, window.innerWidth - tbWidth
- 8]`) so the toolbar always lands fully on-screen even when the
scroll container is wider than the screen.
Scope: visual / positioning only. No changes to TipTap config,
toolbar buttons, or save/cancel logic. Above-vs-below placement
(#581 iOS keyboard handling) is preserved.
Problem: In the "تأجيل الاجتماع" dialog's "إعادة الجدولة" tab, the two
<input type="time"> fields for Start and End rendered as iOS Safari's
native "pill" controls centered inside transparent, hairline-bordered
cells separated by only gap-3. On iPad the pills visually touched in
the middle of the row, so users perceived a single overlapping
control instead of two distinct inputs.
Fix in artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx:
- Increase the grid gap from gap-3 to gap-4 / sm:gap-6 so the two
cells never visually merge regardless of viewport width.
- Add `min-h-11 bg-background border-2` to each Input so each cell
has an explicit, clearly bounded box around the iOS native pill.
- Added an inline #606 comment explaining the iOS quirk so future
edits don't revert the layout.
Scope: layout-only change to the reschedule tab. No changes to
reschedule logic, validation, cascade prompt, or the postpone/cancel
tabs.
Problem: In the "تأجيل الاجتماع" dialog's "إعادة الجدولة" tab, the two
<input type="time"> fields for Start and End rendered as iOS Safari's
native "pill" controls centered inside transparent, hairline-bordered
cells separated by only gap-3. On iPad the pills visually touched in
the middle of the row, so users perceived a single overlapping
control instead of two distinct inputs.
Fix in artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx:
- Increase the grid gap from gap-3 to gap-4 / sm:gap-6 so the two
cells never visually merge regardless of viewport width.
- Add `min-h-11 bg-background border-2` to each Input so each cell
has an explicit, clearly bounded box around the iOS native pill.
- Added an inline #606 comment explaining the iOS quirk so future
edits don't revert the layout.
Scope: layout-only change to the reschedule tab. No changes to
reschedule logic, validation, cascade prompt, or the postpone/cancel
tabs.
Introduce separation of direct and inherited roles in user profiles and API responses. Modify the admin UI to disable the "order receiver" toggle when a role is inherited, providing a clearer user experience. Update API endpoints and schemas to reflect these changes, alongside locale updates for translated strings.
Introduce separation of direct and inherited roles in user profiles and API responses. Modify the admin UI to disable the "order receiver" toggle when a role is inherited, providing a clearer user experience. Update API endpoints and schemas to reflect these changes, alongside locale updates for translated strings.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 33aee19f-8f0f-4399-98e0-39fe09a87e1b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m92e9kU
Replit-Helium-Checkpoint-Created: true
The amber "ملاحظة / Note" button (#600) lives in the Executive
Meetings page header. The whole header is hidden in fullscreen, so the
quick-note panel — which itself already works in fullscreen — had no
entry point. User had to exit fullscreen, add the note, then re-enter.
Added a floating amber pill next to the existing fullscreen-exit pill
at the top-end of the page. Mirrors the header button exactly:
- Same handler (setQuickNoteOpen + setQuickNoteTrigger).
- Same gating: only Schedule section, hidden while the panel is open
or while the bulk-actions toolbar is active.
- Same i18n key (executiveMeetings.quickNote.label) — no new strings.
- Same amber palette (bg-amber-100 / border-amber-300 / text-[#0B1E3F])
with the Plus icon and label.
- Positioned at `top-3 end-14` so it sits inboard of the exit pill
(`end-3`, w-9) in both LTR (to the left) and RTL (to the right),
with no overlap.
- `print:hidden`, `data-testid="em-fullscreen-quicknote"`.
Single JSX addition (~36 lines incl. comment) in
artifacts/tx-os/src/pages/executive-meetings.tsx, right after the
exit-fullscreen button. No other files touched. No new state, no new
i18n, no API changes, no dependency changes. Non-fullscreen layout is
untouched. tx-os tsc passes.
The amber "ملاحظة / Note" button (#600) lives in the Executive
Meetings page header. The whole header is hidden in fullscreen, so the
quick-note panel — which itself already works in fullscreen — had no
entry point. User had to exit fullscreen, add the note, then re-enter.
Added a floating amber pill next to the existing fullscreen-exit pill
at the top-end of the page. Mirrors the header button exactly:
- Same handler (setQuickNoteOpen + setQuickNoteTrigger).
- Same gating: only Schedule section, hidden while the panel is open
or while the bulk-actions toolbar is active.
- Same i18n key (executiveMeetings.quickNote.label) — no new strings.
- Same amber palette (bg-amber-100 / border-amber-300 / text-[#0B1E3F])
with the Plus icon and label.
- Positioned at `top-3 end-14` so it sits inboard of the exit pill
(`end-3`, w-9) in both LTR (to the left) and RTL (to the right),
with no overlap.
- `print:hidden`, `data-testid="em-fullscreen-quicknote"`.
Single JSX addition (~36 lines incl. comment) in
artifacts/tx-os/src/pages/executive-meetings.tsx, right after the
exit-fullscreen button. No other files touched. No new state, no new
i18n, no API changes, no dependency changes. Non-fullscreen layout is
untouched. tx-os tsc passes.
The four default home-screen tiles (Notes / Services / Meetings /
Admin) showed up as three blue tiles + one orange one. Root cause was
NOT the DB — seed.ts already gave each app a distinct hex — but the
`gradientForColor()` mapper in `artifacts/tx-os/src/pages/home.tsx`
only recognised blue/purple/green/orange/pink/teal, so yellow
(#eab308), red (#ef4444), and the dark navy meetings colour all fell
through to the default blue gradient.
Changes:
1. `artifacts/tx-os/src/index.css` — added `.icon-tile-red` and
`.icon-tile-yellow` gradients matching the existing tile style.
2. `artifacts/tx-os/src/pages/home.tsx` — `gradientForColor()` now
maps red/dc2626 and yellow/facc15 first. Purple branch stays for
admin-custom apps but no seeded row uses it.
3. `scripts/src/seed.ts` —
- Meetings: `#0B1E3F` → `#22c55e` (green).
- Notifications: `#8b5cf6` → `#06b6d4` (cyan) — kills the last
purple in the default palette per user direction.
4. `scripts/src/update-app-colors.ts` (new) — idempotent migration
that bumps existing rows ONLY when they still hold the exact old
default colour, so admin customisations from the Apps editor are
preserved. Wired into `scripts/package.json` as
`pnpm run update-app-colors`.
5. `scripts/redeploy.sh` — new step 5/6 runs the colour migration
after seed and before `docker compose up -d`, on the same one-shot
`migrate` container pattern as other one-shot scripts.
No schema changes, no new deps. tx-os tsc passes, scripts tsc passes.
After `./scripts/redeploy.sh` on Mac, home shows four distinct hues
(yellow / orange / green / red) and no tile is purple anywhere.
The four default home-screen tiles (Notes / Services / Meetings /
Admin) showed up as three blue tiles + one orange one. Root cause was
NOT the DB — seed.ts already gave each app a distinct hex — but the
`gradientForColor()` mapper in `artifacts/tx-os/src/pages/home.tsx`
only recognised blue/purple/green/orange/pink/teal, so yellow
(#eab308), red (#ef4444), and the dark navy meetings colour all fell
through to the default blue gradient.
Changes:
1. `artifacts/tx-os/src/index.css` — added `.icon-tile-red` and
`.icon-tile-yellow` gradients matching the existing tile style.
2. `artifacts/tx-os/src/pages/home.tsx` — `gradientForColor()` now
maps red/dc2626 and yellow/facc15 first. Purple branch stays for
admin-custom apps but no seeded row uses it.
3. `scripts/src/seed.ts` —
- Meetings: `#0B1E3F` → `#22c55e` (green).
- Notifications: `#8b5cf6` → `#06b6d4` (cyan) — kills the last
purple in the default palette per user direction.
4. `scripts/src/update-app-colors.ts` (new) — idempotent migration
that bumps existing rows ONLY when they still hold the exact old
default colour, so admin customisations from the Apps editor are
preserved. Wired into `scripts/package.json` as
`pnpm run update-app-colors`.
5. `scripts/redeploy.sh` — new step 5/6 runs the colour migration
after seed and before `docker compose up -d`, on the same one-shot
`migrate` container pattern as other one-shot scripts.
No schema changes, no new deps. tx-os tsc passes, scripts tsc passes.
After `./scripts/redeploy.sh` on Mac, home shows four distinct hues
(yellow / orange / green / red) and no tile is purple anywhere.
PWA + Web Push infra was already fully built (SW, VAPID auto-gen,
subscribe/unsubscribe API, sendPushToUser called from orders/meetings/
notes/replies, iOS-PWA detection hook). The user wasn't getting
lock-screen alerts because iPhone/iPad weren't installed as a PWA —
iOS only delivers Web Push from a Home Screen icon, not a Safari tab.
This commit polishes the iOS install path so the gap is obvious:
1. push-enable-prompt.tsx — Added a dedicated iOS install-steps card
variant that renders on iPhone/iPad Safari when running outside
standalone mode. Shows Share → Add to Home Screen → open from icon.
Independent 14-day dismiss memory from the regular Enable card.
2. notification-settings.tsx — PushToggleRow now detects iOS-non-
standalone and shows the unsupported_ios_safari hint inline with a
disabled toggle, instead of teasing an Enable affordance that always
fails.
3. use-push-subscription.ts — Exported isIosSafariNonStandalone() so
both surfaces share the detection logic (was private).
4. lib/push.ts (server) — Added info logs in loadVapid() for both the
env-key and disk-key paths, removed an accidental duplicate disk-read
block introduced during editing. Now the production redeploy check
is a one-liner: grep for "VAPID keys loaded" in the api logs.
5. ar.json / en.json — Added notifSettings.push.iosInstall.{title,desc,
step1,step2,step3} bilingual strings for the new card.
No DB migrations. No deps changed. tx-os tsc passes; api-server tsc
errors are pre-existing (routes/push.ts handler type, font_settings)
and not touched by this change.
Smoke test on real iPhone/iPad still owed (out-of-band — task step #4
is a manual verification on the user's devices after redeploy).
PWA + Web Push infra was already fully built (SW, VAPID auto-gen,
subscribe/unsubscribe API, sendPushToUser called from orders/meetings/
notes/replies, iOS-PWA detection hook). The user wasn't getting
lock-screen alerts because iPhone/iPad weren't installed as a PWA —
iOS only delivers Web Push from a Home Screen icon, not a Safari tab.
This commit polishes the iOS install path so the gap is obvious:
1. push-enable-prompt.tsx — Added a dedicated iOS install-steps card
variant that renders on iPhone/iPad Safari when running outside
standalone mode. Shows Share → Add to Home Screen → open from icon.
Independent 14-day dismiss memory from the regular Enable card.
2. notification-settings.tsx — PushToggleRow now detects iOS-non-
standalone and shows the unsupported_ios_safari hint inline with a
disabled toggle, instead of teasing an Enable affordance that always
fails.
3. use-push-subscription.ts — Exported isIosSafariNonStandalone() so
both surfaces share the detection logic (was private).
4. lib/push.ts (server) — Added info logs in loadVapid() for both the
env-key and disk-key paths, removed an accidental duplicate disk-read
block introduced during editing. Now the production redeploy check
is a one-liner: grep for "VAPID keys loaded" in the api logs.
5. ar.json / en.json — Added notifSettings.push.iosInstall.{title,desc,
step1,step2,step3} bilingual strings for the new card.
No DB migrations. No deps changed. tx-os tsc passes; api-server tsc
errors are pre-existing (routes/push.ts handler type, font_settings)
and not touched by this change.
Smoke test on real iPhone/iPad still owed (out-of-band — task step #4
is a manual verification on the user's devices after redeploy).
Update the label for the quick note button in the executive meetings header from "Take a note..." to "Note" in English and "اكتب ملاحظة…" to "ملاحظة" in Arabic.
User reported the yellow vertical "اكتب ملاحظة…" tab pinned to the page
edge (left-0 top-[40vh]) felt out of place and asked for it to live
inside the Executive Meetings header in a clear spot.
Changes (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Added an amber-tinted Button in the header action cluster, placed
before the Export PDF button so it reads first in RTL. Uses
bg-amber-100 / border-amber-300 to keep the original yellow visual
identity while matching the other header buttons' shape and size.
Reuses the existing `executiveMeetings.quickNote.label` i18n key —
no new translations. testid: `em-quick-note-header`.
- Same gating as the old floating tab: section === "schedule" &&
!isFullscreen && !bulkToolbarActive && !quickNoteOpen, so the button
hides while the composer is open, in fullscreen, during bulk
selection, and on non-schedule sub-pages.
- Removed the `<MeetingsQuickNoteTab />` mount and the component
definition itself. Updated the stale comment on `bulkToolbarActive`
to refer to the header button.
Verified:
- `rg em-quick-note-tab|MeetingsQuickNoteTab` clean across repo (no
tests referenced the old testid).
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No deviation from the plan.
User reported the yellow vertical "اكتب ملاحظة…" tab pinned to the page
edge (left-0 top-[40vh]) felt out of place and asked for it to live
inside the Executive Meetings header in a clear spot.
Changes (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Added an amber-tinted Button in the header action cluster, placed
before the Export PDF button so it reads first in RTL. Uses
bg-amber-100 / border-amber-300 to keep the original yellow visual
identity while matching the other header buttons' shape and size.
Reuses the existing `executiveMeetings.quickNote.label` i18n key —
no new translations. testid: `em-quick-note-header`.
- Same gating as the old floating tab: section === "schedule" &&
!isFullscreen && !bulkToolbarActive && !quickNoteOpen, so the button
hides while the composer is open, in fullscreen, during bulk
selection, and on non-schedule sub-pages.
- Removed the `<MeetingsQuickNoteTab />` mount and the component
definition itself. Updated the stale comment on `bulkToolbarActive`
to refer to the header button.
Verified:
- `rg em-quick-note-tab|MeetingsQuickNoteTab` clean across repo (no
tests referenced the old testid).
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No deviation from the plan.
In executive-meetings.tsx, the floating exit-fullscreen control was
a pill (`gap-1.5 px-3 py-1.5`) showing both a Minimize2 arrow and
the Arabic/English label "الخروج من ملء الشاشة" / "Exit fullscreen".
User asked for icon only.
Changes:
- Removed the `<span>{t(...)}</span>` label.
- Swapped pill classes for a circular icon button: `inline-flex
items-center justify-center rounded-full bg-[#0B1E3F] text-white
w-9 h-9 shadow-lg hover:bg-[#0B1E3F]/90 print:hidden`, keeping the
same `fixed top-3 end-3 z-50` positioning.
- Bumped the icon to `w-5 h-5` so it reads cleanly on its own.
- Added a `title` attribute (hover tooltip) mirroring the existing
`aria-label` — screen readers + mouse users still get the
localised "exit fullscreen" affordance.
- `data-testid="em-fullscreen-exit"` untouched, so existing tests
keep passing.
- No translation key changes; no change to the enter-fullscreen
toolbar button at L2958.
Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
In executive-meetings.tsx, the floating exit-fullscreen control was
a pill (`gap-1.5 px-3 py-1.5`) showing both a Minimize2 arrow and
the Arabic/English label "الخروج من ملء الشاشة" / "Exit fullscreen".
User asked for icon only.
Changes:
- Removed the `<span>{t(...)}</span>` label.
- Swapped pill classes for a circular icon button: `inline-flex
items-center justify-center rounded-full bg-[#0B1E3F] text-white
w-9 h-9 shadow-lg hover:bg-[#0B1E3F]/90 print:hidden`, keeping the
same `fixed top-3 end-3 z-50` positioning.
- Bumped the icon to `w-5 h-5` so it reads cleanly on its own.
- Added a `title` attribute (hover tooltip) mirroring the existing
`aria-label` — screen readers + mouse users still get the
localised "exit fullscreen" affordance.
- `data-testid="em-fullscreen-exit"` untouched, so existing tests
keep passing.
- No translation key changes; no change to the enter-fullscreen
toolbar button at L2958.
Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.