Commit Graph

863 Commits

Author SHA1 Message Date
Riyadh c20b59e656 Task #590: Daily meetings — clock, inline notes, fullscreen header
Three small UX fixes on /executive-meetings (schedule tab):

1) HeaderClock: dropped the digital "HH:MM:SS م" string next to the
   weekday — the analog dial already conveys the live time, so it was
   redundant. Bumped AnalogClock size from 36 to 44 so the dial reads
   cleanly without the digital crutch. Weekday + date stay (calendar
   info, not clock info). Removed the now-unused `useState`/`useEffect`
   ticker inside HeaderClock — AnalogClock has its own.

2) "اكتب ملاحظة" yellow side tab no longer navigates away to /notes.
   Added a transient `quickNoteOpen` local state in
   ExecutiveMeetingsPageInner. The tab's onClick now toggles it on; a
   new `InlineQuickNotePanel` is rendered above <ScheduleSection> with
   an auto-focused textarea, Cancel/Send buttons, Cmd/Ctrl+Enter to
   submit, Esc to close. Submit POSTs to /api/notes with the existing
   contract ({ content, color: "yellow" }) and closes on success. The
   side tab hides while the panel is open so they don't double up.
   State auto-resets when the user leaves the schedule tab.

3) Fullscreen exit pill overlap: the floating "الخروج من ملء الشاشة"
   pill (fixed top-3 end-3) was covering the schedule table's "الوقت"
   column header. Added `pt-12 sm:pt-14` to the fullscreen branch of
   <main> so the table starts below the pill on iPad landscape
   (1024-wide). Non-fullscreen layout unchanged.

i18n: added executiveMeetings.quickNote.{placeholder,send} in EN+AR;
existing executiveMeetings.quickNote.label reused for the panel
header. All keys also have inline fallbacks so missing translations
don't break.

Drive-by: fixed a TypeScript narrowing error in admin.tsx left over
from the prior task's review-comment cleanup (data.startedAt is
already typed as string, so the typeof check produced `never` in the
else branch — replaced with a direct call).

Verified: pnpm --filter @workspace/tx-os exec tsc --noEmit passes
clean, Vite HMR updated cleanly in dev.
2026-05-18 11:04:27 +00:00
Riyadh bf7c99637e Transitioned from Plan to Build mode 2026-05-18 11:00:50 +00:00
Riyadh 858b9799e8 Add build date and time to system update information
Update admin panel to display build stamp and started at timestamps, fixing `data.current.build` and `data.startedAt` formatting.
2026-05-18 10:40:16 +00:00
Riyadh 608e15cf91 Task #589: Live build stamp + server start time in System Updates panel
Problem: After `git pull && docker compose up -d --build api` on the
Mac, the admin → System Updates panel kept showing the same version
(0.1.0-dev) and the same build (20260517.8a5bd5db) because both come
from version.json, a hand-edited file that nothing rewrites on every
build. No visible signal that a new image was actually running.

Two independent signals were added — neither needs editing version.json
by hand:

1) Auto-stamp version.json at Docker build time
   - docker/api-server.Dockerfile: new ARGs GIT_SHA and BUILD_DATE.
     A `node -e` step rewrites version.json's `build` field to
     `${YYYYMMDDTHHmm}.${sha}` (e.g. 20260518T1042.71da8e01). When
     the args are missing/"unknown", version.json is left untouched
     so plain `docker build` and Replit `pnpm dev` still work.
   - docker-compose.yml: api.build.args wires through GIT_SHA and
     BUILD_DATE with `${GIT_SHA:-unknown}` fallbacks.
   - scripts/redeploy.sh: exports GIT_SHA (git rev-parse --short HEAD)
     and BUILD_DATE (date -u +%Y-%m-%dT%H:%M:%SZ) before `docker
     compose build`, so the helper script just works.

2) startedAt timestamp from the API
   - artifacts/api-server/src/routes/system.ts: SERVER_STARTED_AT
     captured at module load (frozen for the process lifetime) and
     added to every branch of GET /system/version (not-configured,
     error, invalid-version, up-to-date, update-available, catch).
   - lib/api-spec/openapi.yaml: added `startedAt: date-time` to
     SystemVersionResponse (required). Regenerated api-client-react
     and api-zod via `pnpm --filter @workspace/api-spec run codegen`.

3) Admin UI surfacing both signals
   - artifacts/tx-os/src/pages/admin.tsx: new formatBuildStamp parses
     `YYYYMMDD[THHmm].sha` and renders a localized date next to the
     raw token. A new "Server started" line below the build number
     formats the boot timestamp via the existing formatChecked helper.
   - artifacts/tx-os/src/locales/{en,ar}.json: added
     admin.systemUpdates.serverStartedAt ("Server started" /
     "بدأ تشغيل الخادم").

4) Docs
   - replit.md: documented how to verify a real redeploy via the
     System Updates panel and the manual one-liner for `docker
     compose build` without the helper script.

Verified: codegen succeeds, API workflow restarted clean, Vite served
fine. Pre-existing typecheck errors in push.ts and font-settings are
unrelated and untouched.

Mac next steps:
  cd ~/Downloads/TX && git pull && ./scripts/redeploy.sh
Then open admin → System Updates: build line should show a new
`YYYYMMDDTHHmm.sha` stamp and "Server started" should reflect the
new boot time.
2026-05-18 10:39:17 +00:00
Riyadh bbe0482e1c Transitioned from Plan to Build mode 2026-05-18 10:34:58 +00:00
Riyadh 71da8e01e6 Task #588: PDF uses real DIN Next LT Arabic (and other branded fonts)
The HTML/puppeteer PDF renderer in
artifacts/api-server/src/lib/pdf-html-renderer.ts mapped every branded
sans family to NotoSansArabic-*.ttf — so even though the user picked
"DIN Next LT Arabic" in settings, the embedded glyph shapes were Noto
Sans Arabic. Same silent substitution applied to Tajawal, Helvetica
Neue LT Arabic, and Majalla. (pdf-renderer.ts was already wired
correctly to the real files in an earlier change; pdf-html-renderer.ts
was missed.)

Fix in pdf-html-renderer.ts FONT_FILES:
  DIN Next LT Arabic       → DINNextLTArabic-Regular/Bold.ttf
  Tajawal                  → Tajawal-Regular/Bold.ttf
  Helvetica Neue LT Arabic → HelveticaNeueLTArabic-Regular/Bold.ttf
  Majalla                  → Majalla-Regular/Bold.ttf
  system                   → NotoNaskhArabic-Regular/Bold.ttf (unchanged)
  Helvetica Neue           → HelveticaNeue-Regular/Bold.ttf (unchanged)

All target .ttf files were already bundled in
artifacts/api-server/assets/fonts/ — no asset changes needed.

Updated the matching test assertions in
artifacts/api-server/tests/executive-meetings.test.mjs so the
DIN Next LT Arabic path now expects /DINNextLTArabic/ in the PDF
bytes, and the Majalla path expects /Majalla/ instead of
/NotoNaskhArabic/. Kept the negative assertion that Majalla never
embeds a sans face.

API workflow restarted clean. Mac rebuild:
  cd ~/Downloads/TX && git pull && docker compose up -d --build api
2026-05-18 10:26:22 +00:00
Riyadh 71a5b8ed35 Task #588: PDF uses real DIN Next LT Arabic (and other branded fonts)
The HTML/puppeteer PDF renderer in
artifacts/api-server/src/lib/pdf-html-renderer.ts mapped every branded
sans family to NotoSansArabic-*.ttf — so even though the user picked
"DIN Next LT Arabic" in settings, the embedded glyph shapes were Noto
Sans Arabic. Same silent substitution applied to Tajawal, Helvetica
Neue LT Arabic, and Majalla. (pdf-renderer.ts was already wired
correctly to the real files in an earlier change; pdf-html-renderer.ts
was missed.)

Fix in pdf-html-renderer.ts FONT_FILES:
  DIN Next LT Arabic       → DINNextLTArabic-Regular/Bold.ttf
  Tajawal                  → Tajawal-Regular/Bold.ttf
  Helvetica Neue LT Arabic → HelveticaNeueLTArabic-Regular/Bold.ttf
  Majalla                  → Majalla-Regular/Bold.ttf
  system                   → NotoNaskhArabic-Regular/Bold.ttf (unchanged)
  Helvetica Neue           → HelveticaNeue-Regular/Bold.ttf (unchanged)

All target .ttf files were already bundled in
artifacts/api-server/assets/fonts/ — no asset changes needed.

Updated the matching test assertions in
artifacts/api-server/tests/executive-meetings.test.mjs so the
DIN Next LT Arabic path now expects /DINNextLTArabic/ in the PDF
bytes, and the Majalla path expects /Majalla/ instead of
/NotoNaskhArabic/. Kept the negative assertion that Majalla never
embeds a sans face.

API workflow restarted clean. Mac rebuild:
  cd ~/Downloads/TX && git pull && docker compose up -d --build api
2026-05-18 10:25:18 +00:00
Riyadh 109feeab43 Transitioned from Plan to Build mode 2026-05-18 10:24:02 +00:00
Riyadh ff508eeaba Task #587: Fix Docker build — copy version.json into build stage
The api-server docker build failed on the Mac with:
  ERROR: Could not resolve "../../../../version.json"
  src/routes/system.ts:4

`version.json` lives at the repo root and is tracked in git, but
`docker/api-server.Dockerfile` never copied it into the `build` stage,
so esbuild (running in /app/artifacts/api-server) couldn't resolve the
`../../../../version.json` import added by commit cb894f7b.

Fix: added `COPY version.json ./` in the build stage, right after the
tsconfig copy and before `lib`/`scripts`/`artifacts` are copied. No
runtime change is needed — esbuild inlines the JSON into the bundle
via the `with { type: "json" }` import attribute, so the runtime stage
does not need the file on disk.

Local dev (`pnpm --filter @workspace/api-server run dev`) is
unaffected because it builds outside Docker against the real repo
root. tx-os Dockerfile not touched.

Mac side after pulling this commit:
  cd ~/Downloads/TX && git pull && docker compose up -d --build api
2026-05-18 09:54:14 +00:00
Riyadh 05c6f0cd59 Transitioned from Plan to Build mode 2026-05-18 09:53:53 +00:00
Riyadh 314ab887c9 Task #586: Unify PDF column font & row shading
Two small fixes in artifacts/api-server/src/lib/pdf-html-renderer.ts:

1. Removed `font-weight: bold` from `.num-cell` so the # column inherits
   the body font weight (same as meeting/attendees/time columns). The
   header (`thead th`) keeps its bold styling unchanged.

2. Changed attendees and time cells in `buildMeetingRow` to use
   `coloredStyle` (was empty string ""), so when a row has a `rowColor`,
   the tint covers the whole row instead of only the # and meeting
   cells. The `numDarkStyle` (dark bg + white digit for variants with
   a border color) is preserved.

On-screen schedule, merged-cell rows, and cancelled-row red border
behavior are unchanged. API server restarted clean.
2026-05-18 09:47:03 +00:00
Riyadh d96cf8196b Transitioned from Plan to Build mode 2026-05-18 09:45:37 +00:00
Riyadh a7e7233add feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
  centered in the header (both president and normal views). Drop the
  PresidentClockBar render from the toolbar; the dead helper is left
  in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
  sessionStorage persistence, Esc handler, auto-exit when the user
  leaves the schedule tab. Hide the page header when active and show a
  floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
  schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
  left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
  openTrigger that the top Composer reacts to by opening + focusing
  the textarea, then scrub the query param so navigation doesn't
  re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
  executiveMeetings.quickNote.label in ar.json and en.json.

tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
2026-05-18 09:34:32 +00:00
Riyadh 2dbb88308f feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
  centered in the header (both president and normal views). Drop the
  PresidentClockBar render from the toolbar; the dead helper is left
  in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
  sessionStorage persistence, Esc handler, auto-exit when the user
  leaves the schedule tab. Hide the page header when active and show a
  floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
  schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
  left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
  openTrigger that the top Composer reacts to by opening + focusing
  the textarea, then scrub the query param so navigation doesn't
  re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
  executiveMeetings.quickNote.label in ar.json and en.json.

tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
2026-05-18 09:32:55 +00:00
Riyadh ec700dbfbf feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
  centered in the header (both president and normal views). Drop the
  PresidentClockBar render from the toolbar; the dead helper is left
  in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
  sessionStorage persistence, Esc handler, auto-exit when the user
  leaves the schedule tab. Hide the page header when active and show a
  floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
  schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
  left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
  openTrigger that the top Composer reacts to by opening + focusing
  the textarea, then scrub the query param so navigation doesn't
  re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
  executiveMeetings.quickNote.label in ar.json and en.json.

tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
2026-05-18 09:31:11 +00:00
Riyadh ae08bf292a feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
  centered in the header (both president and normal views). Drop the
  PresidentClockBar render from the toolbar; the dead helper is left
  in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
  sessionStorage persistence, Esc handler, auto-exit when the user
  leaves the schedule tab. Hide the page header when active and show a
  floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
  schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
  left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
  openTrigger that the top Composer reacts to by opening + focusing
  the textarea, then scrub the query param so navigation doesn't
  re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
  executiveMeetings.quickNote.label in ar.json and en.json.

tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
2026-05-18 09:29:34 +00:00
Riyadh 04ee3c2c27 feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
  centered in the header (both president and normal views). Drop the
  PresidentClockBar render from the toolbar; the dead helper is left
  in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
  sessionStorage persistence, Esc handler, auto-exit when the user
  leaves the schedule tab. Hide the page header when active and show a
  floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
  schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
  left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
  openTrigger that the top Composer reacts to by opening + focusing
  the textarea, then scrub the query param so navigation doesn't
  re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
  executiveMeetings.quickNote.label in ar.json and en.json.

tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
2026-05-18 09:28:02 +00:00
Riyadh b6e26f47f3 feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
  centered in the header (both president and normal views). Drop the
  PresidentClockBar render from the toolbar; the dead helper is left
  in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
  sessionStorage persistence, Esc handler, auto-exit when the user
  leaves the schedule tab. Hide the page header when active and show a
  floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
  schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
  left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
  openTrigger that the top Composer reacts to by opening + focusing
  the textarea, then scrub the query param so navigation doesn't
  re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
  executiveMeetings.quickNote.label in ar.json and en.json.

tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
2026-05-18 09:26:12 +00:00
Riyadh 29de565d55 Transitioned from Plan to Build mode 2026-05-18 09:19:46 +00:00
Riyadh 57a4a72739 Improve toolbar positioning to avoid keyboard obstruction
Update toolbar positioning logic to flip above the editor when necessary to prevent it from being hidden by the visual viewport or the software keyboard, addressing issue #581.
2026-05-18 08:30:20 +00:00
Riyadh 53d637aa35 editable-cell: keep formatting toolbar above the iPad keyboard (#581)
Bug: on iPad / iOS Safari, tapping a meetings-table editable cell hid
the floating formatting toolbar (color swatches, alignment, font, save,
cancel) under the soft keyboard. Users saw only Apple's tiny B/I/U
accessory bar and could not change color or even tap Save/Cancel.

Root cause in `artifacts/tx-os/src/components/editable-cell.tsx`:
FormattingToolbar positioned itself at `rect.bottom + 4` and explicitly
refused to flip above the cell. It also only listened to window scroll
and resize, not to `visualViewport` events, so it did not react to the
keyboard opening or closing.

Fix (single file):
- Import and call the existing `useVisualViewport` hook inside
  FormattingToolbar to get reactive `{ height, offsetTop }`.
- Compute the visible viewport bottom as `offsetTop + height` and, when
  the toolbar would not fit below the cell without going under that
  bottom (keyboard or short viewport), flip it ABOVE the cell:
  `top = max(vv.offsetTop + 4, rect.top - tbHeight - 4)`.
- Keep desktop behavior unchanged: when the viewport is full-height the
  below position still fits, so we still place below the cell.
- Added `vv.height`, `vv.offsetTop`, `vv.keyboardInset` to the effect
  deps so the toolbar tracks the keyboard show/hide animation.
- Replaced the "never flip above" comment block with a comment that
  documents the keyboard-coverage exception.

Out of scope (unchanged): toolbar wrapping/overflow on very narrow
iPads, Apple's built-in B/I/U keyboard accessory bar, any other page.

Validation:
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
- Vite HMR updated editable-cell.tsx without errors.
2026-05-18 08:29:22 +00:00
Riyadh 743309ca36 Transitioned from Plan to Build mode 2026-05-18 08:28:09 +00:00
Riyadh 08af3fca72 notes: file-manager layout, folders section header, kill grid gap (#578)
Cleans up the Notes page after a user creates their first folder:

1. FoldersRail: in `rail` mode (every view except "All notes"), insert a
   small "My folders" / "مجلداتي" section header above the user's folder
   list so it no longer visually nests under the dark "Unfiled" pill.
   New i18n key `notes.folders.myFolders` in en.json and ar.json.

2. notes.tsx filteredNotes: when `folderSelection.kind === "all"`, hide
   notes whose `folderId !== null`. Combined with the existing
   FoldersBrowser cards this gives a file-manager feel — notes inside a
   folder are reachable by opening the folder card and no longer appear
   in the flat list, so users stop thinking move = copy. Counts on the
   "All notes" rail entry stay as the total (totalCount prop unchanged).

3. notes.tsx grid: replaced the two separate grid cells for
   composer/content with a single wrapper that uses `display: contents`
   on mobile (children flow into the outer grid honoring order-1/2/3)
   and `flex flex-col gap-4` on md+ (composer and content stack in the
   same cell, no empty grid-row gap when the FoldersRail is taller than
   the composer). Removed unused conditional row-start/row-span on the
   inner content div.

Out of scope per plan: dnd-kit behavior, shared-with-me logic, composer
or folder card redesign.

Validation: `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
Vite HMR successful after fix; no babel/JSX errors.
2026-05-18 08:00:28 +00:00
Riyadh eb1ed3a3e3 Transitioned from Plan to Build mode 2026-05-18 07:55:18 +00:00
Riyadh 6b7f238f40 #577: clean up reschedule dialog layout on iPad landscape
The Postpone dialog's "إعادة الجدولة" tab was packing Date + Start +
End into a single 3-column row at `lg:` (≥1024 px), which on iPad
landscape (and small desktops) made iOS Safari's native date/time
spinner glyphs + Arabic AM/PM markers visibly collide between cells.
User reported the boxes looked "غير مرتبه".

Change:
- artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx:
  replace the `grid grid-cols-1 gap-3 lg:grid-cols-3` wrapper with a
  vertical `space-y-3` stack: Date input on its own full-width row,
  then a `grid grid-cols-2 gap-3` row holding Start + End side-by-
  side. Same `min-w-0` + `w-full` discipline on every cell so inputs
  respect their track.
- Updated the explanatory comment to record why we no longer try a
  3-column path: the only layout that stays clean from 320 px phones
  up through desktop without depending on a custom picker is the
  Date-on-top / Start+End-below stack.

Verified `tsc --noEmit` clean. No DB / locale / API changes. Mac
rebuild = `git pull && ./start.sh rebuild && docker compose up -d
--force-recreate web`. No script run needed.
2026-05-18 07:49:36 +00:00
Riyadh a69eed4710 Transitioned from Plan to Build mode 2026-05-18 07:48:49 +00:00
Riyadh 1cef84bc23 Disable unwanted default applications and clean up related scripts
Remove `enable-default-apps.ts` script, add `disable-deprecated-apps.ts`, update seeding logic in `seed.ts` to set `isActive` to false for documents, and modify `DOCKER.md` and `scripts/package.json` accordingly.
2026-05-18 07:18:15 +00:00
Riyadh e44c9a8976 Improve handling of notification permission prompts
Update notification permission logic to differentiate between denied and dismissed states, and adjust locale messages accordingly.
2026-05-17 17:21:18 +00:00
Riyadh bf5bfa29c4 #576: diagnose push enable + open full edit page from quick actions
Three connected fixes on the Executive Meetings schedule.

1) Push enable diagnostics (use-push-subscription.ts):
   - `enable()` now returns { ok, reason } instead of a bare boolean.
   - Reasons: unsupported, unsupported_ios_safari, permission_denied,
     vapid_unavailable, subscribe_failed, server_rejected.
   - Detects iPhone/iPad Safari not running as a PWA (checks
     navigator.standalone + display-mode media query, with iPadOS
     MacIntel + maxTouchPoints fallback) and short-circuits with
     unsupported_ios_safari so the user gets a concrete
     "open from Home Screen first" message instead of a silent fail.
   - Wrapped VAPID fetch and pushManager.subscribe in their own
     try/catches so each failure mode maps to its own reason.

2) Specific failure toasts (push-enable-prompt.tsx,
   notification-settings.tsx):
   - Both call sites now look up
     `notifSettings.push.errors.<reason>` and fall back to the
     legacy generic copy if the key is missing.
   - Added enableSuccess toast on the toggle path too — previously
     only the prompt-card path showed feedback on success.
   - New AR + EN strings under notifSettings.push.errors.*.

3) Quick-actions row sheet (executive-meetings.tsx):
   - Edit + Postpone buttons now sit SIDE BY SIDE on every viewport
     (flex + flex-1 + min-w-0 instead of flex-wrap + min-w-[11rem]),
     same height, equal width — fixes the stacked / over-sized Edit
     button shown in the user's iPhone screenshot.
   - "Edit" no longer opens the small Meeting edit Dialog. It now
     flips the whole schedule into edit mode (same as the toolbar
     pencil) and paints a transient accent ring on the originating
     row (~1.6s) + smooth-scrolls it into view. Matches image 3 in
     #576 where the user expects to see the full editable list.
   - Added `highlightEdit` prop on MeetingRow that composes an
     extra inset+outer ring on top of the existing quickOpen /
     selection / current / press shadows, plus a soft background
     tint, and clears automatically when ScheduleSection clears
     highlightEditRowId.

Out of scope (left for follow-up tasks): server-side push delivery
(payloads/sounds), the edit-mode UI itself.
2026-05-17 17:19:42 +00:00
Riyadh 160c76b2ce Transitioned from Plan to Build mode 2026-05-17 17:16:07 +00:00
Riyadh 81acbc2710 #575: IT-P brand icon + login hero image
Replace generic PWA/favicon assets with the IT-P brand image and layer
the same image over the login page's AnimatedArt panel.

- Generated icon-192/512, maskable-512, and apple-touch-icon (180) from
  the attached IT-P photo via ImageMagick (center crop 1110x1110, then
  resize). Maskable has #0B1E3F padding so the safe zone is respected
  on Android adaptive icons.
- Copied source as public/brand-itp.jpg for the login hero.
- index.html: replaced the generic favicon.svg link with a full
  favicon set built from IT-P (favicon.ico + 16/32/48/192/512 PNG),
  all cache-busted with ?v=itp1. Deleted public/favicon.svg so no
  browser can fall back to the old generic mark.
- Cache-busted apple-touch-icon link the same way so iPad PWA
  re-installs pick up the new artwork.
- manifest.webmanifest: same ?v=itp1 cache-bust on all icon entries.
- login.tsx: layered IT-P image over AnimatedArt with mix-blend-screen
  (opacity 90%) plus top/bottom and right-edge dark gradients so the
  composition reads as one piece and the right edge fades cleanly into
  the form panel. Still hidden on < lg per scope.

Did not change favicon.svg itself (kept as last-resort vector
fallback). Mobile (< lg) shows no hero — explicit choice per plan
("إخفاؤها بشكل مدروس").
2026-05-17 16:09:24 +00:00
Riyadh 425e09a20d #575: IT-P brand icon + login hero image
Replace generic PWA/favicon assets with the IT-P brand image and layer
the same image over the login page's AnimatedArt panel.

- Generated icon-192/512, maskable-512, and apple-touch-icon (180) from
  the attached IT-P photo via ImageMagick (center crop 1110x1110, then
  resize). Maskable has #0B1E3F padding so the safe zone is respected
  on Android adaptive icons.
- Copied source as public/brand-itp.jpg for the login hero.
- index.html: added explicit PNG icon links (192, 512) before the
  existing favicon.svg fallback, plus cache-bust query (?v=itp1) on
  apple-touch-icon so iPad PWA re-installs pick up the new artwork.
- manifest.webmanifest: same ?v=itp1 cache-bust on all icon entries.
- login.tsx: layered IT-P image over AnimatedArt with mix-blend-screen
  (opacity 90%) plus top/bottom and right-edge dark gradients so the
  composition reads as one piece and the right edge fades cleanly into
  the form panel. Still hidden on < lg per scope.

Did not change favicon.svg itself (kept as last-resort vector
fallback). Mobile (< lg) shows no hero — explicit choice per plan
("إخفاؤها بشكل مدروس").
2026-05-17 16:08:28 +00:00
Riyadh dd2259777f Transitioned from Plan to Build mode 2026-05-17 16:07:02 +00:00
Riyadh 3d06ceef4d Show the notifications app on the main page again
Remove hardcoded exclusion for the notifications app in the home page component.
2026-05-17 15:42:29 +00:00
Riyadh 61701b422c Enable calendar and notification apps by default
Add a new script to enable the calendar and notification applications by default, along with documentation updates.
2026-05-17 15:19:51 +00:00
Riyadh cb894f7baa Add automatic build version generation from git
Implement a script to automatically generate build version information from git commit hash and date, updating version.json and creating latest.json.
2026-05-17 15:16:02 +00:00
Riyadh 8a5bd5db51 Add system updates page to admin section
Add a new API endpoint and UI section for checking system updates, including internationalized locale strings and necessary dependency updates.
2026-05-17 14:59:36 +00:00
Riyadh 0e423a047f #571: ship Calendar/Notifications disabled by default + drop Notifications tile from home
Task: on fresh installs the Calendar and Notifications apps were
appearing enabled by default, and the home screen showed a
Notifications tile that was redundant with the bell icon already in
the top bar. User wanted both apps disabled by default and the home
tile gone (bell stays).

Changes:
- scripts/src/seed.ts: flip the seed `isActive` for `notifications`
  and `calendar` from true → false. The seed inserts apps with
  `db.insert(appsTable).values(apps).onConflictDoNothing()`, so this
  is fully idempotent: existing environments (where the row already
  exists with isActive=true) are NOT changed; only fresh inserts on
  new installs pick up the disabled default. Admins can enable
  either app from app settings when they want it.
- artifacts/tx-os/src/pages/home.tsx: remove "notifications" from
  the `dockApps` filter so the bottom dock no longer renders a
  Notifications tile. Filter becomes `["services", "admin"]`. The
  top-bar bell icon (and its unread badge) was untouched and still
  routes to `/notifications`.

Out of scope (per spec): no migration to disable existing prod
rows; no role/permission changes; bell icon in the top bar stays.

Files:
- scripts/src/seed.ts (notifications block ~L229, calendar block ~L275)
- artifacts/tx-os/src/pages/home.tsx:532
2026-05-17 14:43:54 +00:00
Riyadh 8a629007e8 #571: ship Calendar/Notifications disabled by default + drop Notifications tile from home
Task: on fresh installs the Calendar and Notifications apps were
appearing enabled by default, and the home screen showed a
Notifications tile that was redundant with the bell icon already in
the top bar. User wanted both apps disabled by default and the home
tile gone (bell stays).

Changes:
- scripts/src/seed.ts: flip the seed `isActive` for `notifications`
  and `calendar` from true → false. The seed inserts apps with
  `db.insert(appsTable).values(apps).onConflictDoNothing()`, so this
  is fully idempotent: existing environments (where the row already
  exists with isActive=true) are NOT changed; only fresh inserts on
  new installs pick up the disabled default. Admins can enable
  either app from app settings when they want it.
- artifacts/tx-os/src/pages/home.tsx: remove "notifications" from
  the `dockApps` filter so the bottom dock no longer renders a
  Notifications tile. Filter becomes `["services", "admin"]`. The
  top-bar bell icon (and its unread badge) was untouched and still
  routes to `/notifications`.

Out of scope (per spec): no migration to disable existing prod
rows; no role/permission changes; bell icon in the top bar stays.

Files:
- scripts/src/seed.ts (notifications block ~L229, calendar block ~L275)
- artifacts/tx-os/src/pages/home.tsx:532
2026-05-17 14:42:48 +00:00
Riyadh e79a8dee41 #570: stronger selected-row highlight + Edit button in Quick Actions
Task: on iPad the bulk-selected row was nearly invisible (only a 2px
inset navy ring), and the row-tap Quick Actions dialog had only a
Postpone button — user asked for a real visible selection fill and
an Edit (pencil) action wired to the existing edit-meeting flow.

Changes (artifacts/tx-os/src/pages/executive-meetings.tsx):
- MeetingRow: add `selectionBg = rgba(11,30,63,0.18)` painted on the
  row's `<tr>` backgroundColor in addition to the existing
  `selectionShadow` 2px navy ring. Composition order in trStyle is
  `quickOpenBg ?? selectionBg ?? baseBg` so an open quick-actions
  surface still wins (transient action state), user-picked row
  colours still show when nothing is selected, and the navy tint
  reads clearly on iPad against the table grid.
- MeetingRow: new optional `onQuickEdit?: () => void` prop. Quick
  Actions Dialog gains a pencil-icon "Edit" button before the
  existing Postpone button, wrapped in `flex flex-wrap gap-2` so the
  two buttons sit side-by-side on iPad/desktop and stack on narrow
  viewports. Button only renders when a handler is wired
  (defensive — `quickActionsCanMutate` already gates the surface).
- ScheduleSection: host a local `editingMeeting` + `editingSaving`
  state plus a `MeetingFormDialog` mount, with a save handler whose
  body mirrors ManageSection's `save()` exactly (same PATCH payload
  projection, same `wrapAsParagraph` / attendee shaping, same
  toast/error translation). Edit button on a row opens this in-place
  dialog instead of switching the user to the Manage tab.
- Locale: add `executiveMeetings.quickActions.edit` = "تعديل" / "Edit".

Notes:
- No new icon import needed — `Pencil` was already in the lucide
  import group.
- Selection ring (`#0B1E3F`) and tint use the same navy so they
  read as one cohesive selected state, not competing layers.
- Pre-existing `use-push-subscription.ts` TS noise unchanged.
2026-05-17 14:40:34 +00:00
Riyadh ca77ad1790 Hide redundant clock and navigation elements for president view
Modify executive-meetings.tsx to conditionally hide the HeaderClock and section navigation when isPresidentView is true.
2026-05-17 14:34:59 +00:00
Riyadh ae3e17a97e #572 president-only focused view for executive meetings
User asked that when the President (`executive_ceo` role) logs into
the Meetings page he sees a clean "presentation" layout: just the
schedule table, an analog clock plus the weekday/date of the
schedule, and nothing else. The Manage / Notifications / Audit /
PDF tabs and the PDF-export shortcut button are noisy for him and
must disappear — but his backend permissions stay intact.

Changes (frontend only — backend role gates untouched):
- New component `components/executive-meetings/analog-clock.tsx`:
  small SVG analog clock (hour/minute/second hands, navy + gold),
  ticks every second, ~44px default.
- New inner `PresidentClockBar` component in `executive-meetings.tsx`:
  renders the analog clock + weekday + **digital time** (ticking
  per second, localized) + selected schedule date in one block.
- `pages/executive-meetings.tsx`:
  - Derived `isPresidentView` from the `/me` roles: true iff
    `executive_ceo` is present AND the user is NOT also `admin`
    or `executive_office_manager` (admins keep full UI).
  - Effect forces `section = "schedule"` whenever
    `isPresidentView` becomes true, so the president can never
    land on a now-hidden tab.
  - Hides the header PDF-shortcut button and the entire SECTIONS
    tab list when `isPresidentView`. The `#em-toolbar-portal` is
    kept so the date picker still mounts.
  - `ScheduleSection` accepts two new optional props:
    `isPresidentView` and `lang`. When `isPresidentView` is on,
    the toolbar renders the AnalogClock next to a label showing
    `formatWeekday(date, lang, "long")` and `formatDate(date, lang)`
    (e.g. "Sunday · May 17, 2026" / "الأحد · 17 مايو 2026"). The
    label updates immediately when the date input changes.
  - Edit-mode toggle stays gated on `canMutate`, so the president
    (who has canMutate) still gets the inline-edit pencil if he
    wants it.

Out of scope: backend role changes, applying the focused view to
other roles, manual presentation-mode toggle. Verified via tsc
(only the pre-existing `use-push-subscription` error remains).
2026-05-17 14:34:12 +00:00
Riyadh b52c54eefe #572 president-only focused view for executive meetings
User asked that when the President (`executive_ceo` role) logs into
the Meetings page he sees a clean "presentation" layout: just the
schedule table, an analog clock plus the weekday/date of the
schedule, and nothing else. The Manage / Notifications / Audit /
PDF tabs and the PDF-export shortcut button are noisy for him and
must disappear — but his backend permissions stay intact.

Changes (frontend only — backend role gates untouched):
- New component `components/executive-meetings/analog-clock.tsx`:
  small SVG analog clock (hour/minute/second hands, navy + gold),
  ticks every second, ~44px default.
- `pages/executive-meetings.tsx`:
  - Derived `isPresidentView` from the `/me` roles: true iff
    `executive_ceo` is present AND the user is NOT also `admin`
    or `executive_office_manager` (admins keep full UI).
  - Effect forces `section = "schedule"` whenever
    `isPresidentView` becomes true, so the president can never
    land on a now-hidden tab.
  - Hides the header PDF-shortcut button and the entire SECTIONS
    tab list when `isPresidentView`. The `#em-toolbar-portal` is
    kept so the date picker still mounts.
  - `ScheduleSection` accepts two new optional props:
    `isPresidentView` and `lang`. When `isPresidentView` is on,
    the toolbar renders the AnalogClock next to a label showing
    `formatWeekday(date, lang, "long")` and `formatDate(date, lang)`
    (e.g. "Sunday · May 17, 2026" / "الأحد · 17 مايو 2026"). The
    label updates immediately when the date input changes.
  - Edit-mode toggle stays gated on `canMutate`, so the president
    (who has canMutate) still gets the inline-edit pencil if he
    wants it.

Out of scope: backend role changes, applying the focused view to
other roles, manual presentation-mode toggle. Verified via tsc
(only the pre-existing `use-push-subscription` error remains).
2026-05-17 14:32:19 +00:00
Riyadh 1b88991746 Transitioned from Plan to Build mode 2026-05-17 14:30:14 +00:00
Riyadh 46414ec4bd #568 silent delete for finished orders
User requested that deleting a finished order from My Orders happen
silently — one click on the trash icon, no confirmation dialog, no
success toast. Just click and the card disappears.

Changes:
- Removed the delete-confirm AlertDialog from OrderCard along with
  its `confirmDeleteOpen` state. The trash icon now calls
  `handleDelete` directly.
- Removed `toast({ title: t("myOrders.deleted") })` from
  `scheduleDelete`. The 7-second soft-delete grace window stays in
  place (pendingDeleteIds filter + setTimeout) so the API DELETE
  still fires after the window. The `deleteFailed` error toast is
  preserved so silent API failures don't lose orders.
- Dropped the now-unused locale keys from ar.json and en.json:
  `deleteConfirmTitle`, `deleteConfirmBody`, `deleteConfirm`,
  `deleted`.
- Removed the unused `AlertDialogDescription` import (the remaining
  cancel-confirm dialog uses only the title).

Cancel-order flow remains untouched (still shows confirm dialog and
undo toast as in #566).
2026-05-17 14:13:42 +00:00
Riyadh e487c879f0 Transitioned from Plan to Build mode 2026-05-17 14:12:17 +00:00
Riyadh 54dc04f12d #566 clean up My Orders page (3 removals)
Per user request on the طلباتي page:

1. Removed the "Clear finished orders" bulk button entirely. The button
   was showing as a raw i18n key (myOrder c...) for the user and they
   asked for it gone. Dropped the related AlertDialog, confirmClearOpen
   state, handleClearFinished handler, and the locale keys
   clearFinished_*, clearConfirmTitle/Body_*, clearConfirm,
   clearedCount_*, and clearedPartial in both ar.json and en.json.

2. Removed the "تراجع / Undo" ToastAction shown after deleting an
   order. The toast now displays only the "Order deleted" title; the
   7-second timer + pendingDeleteIds soft-delete window is preserved
   so the actual DELETE still fires after the grace period. The undo
   closure was deleted since nothing references it anymore. Single-id
   call site stays untouched (scheduleDelete still receives [id]).

3. Removed the "ستتاح لك ثوانٍ قليلة للتراجع" description from the
   cancel-order confirmation dialog. The cancelConfirmBody key was
   dropped from both locales. The AlertDialog now shows only the
   title + the two action buttons.

Out of scope (kept as-is):
- The undo ToastAction after CANCEL (user only asked about delete).
- Incoming-orders page.

Notes:
- ToastAction import is still used by the cancel-undo path.
- Trash2 import still used by the per-card delete button.
- Pre-existing TS error in use-push-subscription.ts is unrelated.
2026-05-17 14:07:23 +00:00
Riyadh 367daa444b Transitioned from Plan to Build mode 2026-05-17 14:05:04 +00:00
Riyadh 203312abe4 fix(executive-meetings): quick-actions UX + reschedule/cancel polish (Task #563)
User-reported polish on the executive-meetings quick-actions flow on
iPad/mobile. Six fixes, all client-side:

1. Quick-actions surface now opens from ANY click on a meeting row
   (was: only the attendees cell). Root cause: handleRowClick guard
   skipped any `em-edit-*`, `em-merge-edit-*`, and `em-time-*`
   descendants. Those affordances only do anything in edit mode, but
   handleRowClick already early-returns when edit mode is on, so the
   testid guard was always over-aggressive. Dropped those guards plus
   the `[role='button']` rule from the interactive selector, kept the
   real `button`/`input`/`textarea`/`a`/`menuitem`/`checkbox`/etc.
   guards and the `em-row-actions-*` / `em-row-select-*` testid
   guards (those affordances are visible outside edit mode and must
   not trigger the surface).
   File: artifacts/tx-os/src/pages/executive-meetings.tsx (~L4350).

2. Originating row is now unmistakable on iPad: quickOpenShadow is
   `inset 0 0 0 4px highlight, 0 0 0 2px highlight` (was a single
   3px inset) and the row background tints to
   `rgba(highlight, 0.08)` while quick-actions is open.
   File: artifacts/tx-os/src/pages/executive-meetings.tsx
   (~L4287, L4344).

3. Postpone dialog header was overlapping the Radix close X in RTL.
   Added `pr-8` on the DialogHeader (same pattern already used for
   the row-quick mini-dialog).
   File: artifacts/tx-os/src/components/executive-meetings/
   upcoming-meeting-alert.tsx (~L1447).

4. Reschedule tab: date / start / end inputs collided on iPad
   portrait. Grid is now `grid-cols-1 md:grid-cols-3` (single column
   through iPad portrait, three across on landscape/desktop), with
   `min-w-0` on each cell and `w-full` on every Input so Safari's
   native date/time pickers stop overflowing.
   Same file (~L1761).

5. Cancel tab X overlap is resolved by the same DialogHeader fix in
   step 3 (one Dialog wraps all three tabs).

6. Removed the `cancelHint` paragraph + the `cancelHint` translation
   keys in ar.json (L1252) and en.json (L1164) — fully unused now.

NOT pushed to Gitea (Tailscale to desktop-11cj93j still flaky).
Pending pushes: #560 (VAPID), #561 (CEO roles), #563 (this).
2026-05-17 10:19:16 +00:00
Riyadh 55d6e65456 fix(executive-meetings): quick-actions UX + reschedule/cancel polish (Task #563)
User-reported polish on the executive-meetings quick-actions flow on
iPad/mobile. Six fixes, all client-side:

1. Quick-actions surface now opens from ANY click on a meeting row
   (was: only the attendees cell). Root cause: handleRowClick guard
   skipped any `em-edit-*`, `em-merge-edit-*`, and `em-time-*`
   descendants. Those affordances only do anything in edit mode, but
   handleRowClick already early-returns when edit mode is on, so the
   testid guard was always over-aggressive. Dropped those guards plus
   the `[role='button']` rule from the interactive selector, kept the
   real `button`/`input`/`textarea`/`a`/`menuitem`/`checkbox`/etc.
   guards and the `em-row-actions-*` / `em-row-select-*` testid
   guards (those affordances are visible outside edit mode and must
   not trigger the surface).
   File: artifacts/tx-os/src/pages/executive-meetings.tsx (~L4350).

2. Originating row is now unmistakable on iPad: quickOpenShadow is
   `inset 0 0 0 4px highlight, 0 0 0 2px highlight` (was a single
   3px inset) and the row background tints to
   `rgba(highlight, 0.08)` while quick-actions is open.
   File: artifacts/tx-os/src/pages/executive-meetings.tsx
   (~L4287, L4344).

3. Postpone dialog header was overlapping the Radix close X in RTL.
   Added `pr-8` on the DialogHeader (same pattern already used for
   the row-quick mini-dialog).
   File: artifacts/tx-os/src/components/executive-meetings/
   upcoming-meeting-alert.tsx (~L1447).

4. Reschedule tab: date / start / end inputs collided on iPad
   portrait. Grid is now `grid-cols-1 md:grid-cols-3` (single column
   through iPad portrait, three across on landscape/desktop), with
   `min-w-0` on each cell and `w-full` on every Input so Safari's
   native date/time pickers stop overflowing.
   Same file (~L1761).

5. Cancel tab X overlap is resolved by the same DialogHeader fix in
   step 3 (one Dialog wraps all three tabs).

6. Removed the `cancelHint` paragraph + the `cancelHint` translation
   keys in ar.json (L1252) and en.json (L1164) — fully unused now.

NOT pushed to Gitea (Tailscale to desktop-11cj93j still flaky).
Pending pushes: #560 (VAPID), #561 (CEO roles), #563 (this).
2026-05-17 10:18:13 +00:00