Commit Graph

1024 Commits

Author SHA1 Message Date
riyadhafraa 53c50575e1 Add Arabic version of system architecture diagram
Add a new image output for the Arabic version of the Tx OS architecture diagram, aliased as "tx_os_architecture_ar.png".

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c2b163e2-3155-457b-b364-4e161de5010a
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
2026-05-18 18:10:12 +00:00
riyadhafraa beaa5fd61f Add architecture diagram to documentation for system overview
Add a new image asset for the Tx OS architecture diagram to the agent assets metadata.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: bb62dba0-f271-4e9f-8557-8aea537e7d67
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
2026-05-18 17:56:34 +00:00
riyadhafraa 918485411f Task #602: quick-note button in schedule fullscreen mode
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.
2026-05-18 13:59:02 +00:00
riyadhafraa 4290bc54c9 Task #603: distinct, non-purple home-tile colours
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.
2026-05-18 13:52:42 +00:00
riyadhafraa bcffb2a532 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 024188b7-acb4-4903-960e-ff881a183361
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
2026-05-18 13:50:04 +00:00
riyadhafraa a96b2570e3 Task #601: iOS PWA push notifications enablement
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).
2026-05-18 13:13:12 +00:00
riyadhafraa f2bb1dad33 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 54365c3f-a379-4b01-96b3-4da7bd2d0385
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
2026-05-18 13:08:59 +00:00
riyadhafraa 475415b542 Update quick note button label to be shorter
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.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1f9a6661-3f64-45ae-b766-13143669519d
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
2026-05-18 13:01:59 +00:00
riyadhafraa 504a8d4a64 #600: move quick-note entry from floating side tab into meetings header
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.
2026-05-18 12:58:30 +00:00
riyadhafraa 6dda7d7120 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b600dbe8-4b1c-4269-a12e-f472ef92966b
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
2026-05-18 12:57:30 +00:00
riyadhafraa 65a50444f0 Rearrange header clock to display time vertically below date and weekday
Update the header clock component to a vertical layout, displaying weekday, date, and then time.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 88ce34d5-ccee-404b-a0e0-7e07876438f9
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
2026-05-18 12:54:49 +00:00
riyadhafraa 5dcf949173 Update meeting header to show live digital time
Replace analog clock with a live digital clock display using useNow and formatTimeI18n.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b1d23dc1-fa04-4c00-95be-572d8f7be20b
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
2026-05-18 12:52:39 +00:00
riyadhafraa 46b1d59466 #599 fullscreen-exit button: icon only, no text label
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.
2026-05-18 12:34:39 +00:00
riyadhafraa 015b28c99a Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 05b2393f-c0f6-449c-86a6-8f3bf8c6fa07
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
2026-05-18 12:34:00 +00:00
riyadhafraa df2559f7cb #598 global "other apps" bottom dock + remove duplicate #596 gear
New shared component `artifacts/tx-os/src/components/app-dock.tsx`:
takes a `currentSlug` prop, pulls the apps list from the same
`useListApps()` query Home uses, filters out the current app and any
inactive ones, and renders a floating centered pill near the bottom
with one circular tinted button per remaining app. Reuses Home's
`resolveIcon(name)` pattern (Lucide string -> component, falls back to
Grid2X2) and Home's openApp logic (internal -> setLocation, external
_tab -> window.open, external_iframe -> /embedded/:id). Renders
nothing when fewer than 1 other apps are available. Hidden in print,
respects safe-area-inset-bottom, scrollable on narrow widths.

Mounted on every app page except Home:
- executive-meetings.tsx (currentSlug="executive-meetings", matches
  seed slug; hidden when isFullscreen is true so the schedule fills
  the screen unobstructed).
- services.tsx (currentSlug="services")
- notes.tsx (currentSlug="notes")
- orders-incoming.tsx (currentSlug="orders-incoming" — not a built-in
  app slug, so all apps show, which is the right UX since incoming
  inbox is reached via the topbar, not the home grid).
- admin.tsx (currentSlug="admin")

Removed the two gear/back-arrow blocks added by #596 in
executive-meetings.tsx (test ids em-president-settings,
em-president-back-to-schedule) — the dock now provides cross-app
navigation including Admin/Settings. The L902 force-schedule effect
that snaps the president back to the schedule section outside of
schedule|settings is left in place; it still gates the hidden
Manage / Audit tabs.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No backend, no new translations, Home untouched.
2026-05-18 12:31:16 +00:00
riyadhafraa b43357439c #598 global "other apps" bottom dock + remove duplicate #596 gear
New shared component `artifacts/tx-os/src/components/app-dock.tsx`:
takes a `currentSlug` prop, pulls the apps list from the same
`useListApps()` query Home uses, filters out the current app and any
inactive ones, and renders a floating centered pill near the bottom
with one circular tinted button per remaining app. Reuses Home's
`resolveIcon(name)` pattern (Lucide string -> component, falls back to
Grid2X2) and Home's openApp logic (internal -> setLocation, external
_tab -> window.open, external_iframe -> /embedded/:id). Renders
nothing when fewer than 1 other apps are available. Hidden in print,
respects safe-area-inset-bottom, scrollable on narrow widths.

Mounted on every app page except Home:
- executive-meetings.tsx (currentSlug="executive-meetings", matches
  seed slug; hidden when isFullscreen is true so the schedule fills
  the screen unobstructed).
- services.tsx (currentSlug="services")
- notes.tsx (currentSlug="notes")
- orders-incoming.tsx (currentSlug="orders-incoming" — not a built-in
  app slug, so all apps show, which is the right UX since incoming
  inbox is reached via the topbar, not the home grid).
- admin.tsx (currentSlug="admin")

Removed the two gear/back-arrow blocks added by #596 in
executive-meetings.tsx (test ids em-president-settings,
em-president-back-to-schedule) — the dock now provides cross-app
navigation including Admin/Settings. The L902 force-schedule effect
that snaps the president back to the schedule section outside of
schedule|settings is left in place; it still gates the hidden
Manage / Audit tabs.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No backend, no new translations, Home untouched.
2026-05-18 12:30:06 +00:00
riyadhafraa 264f52827d #598 global "other apps" bottom dock + remove duplicate #596 gear
New shared component `artifacts/tx-os/src/components/app-dock.tsx`:
takes a `currentSlug` prop, pulls the apps list from the same
`useListApps()` query Home uses, filters out the current app and any
inactive ones, and renders a floating centered pill near the bottom
with one circular tinted button per remaining app. Reuses Home's
`resolveIcon(name)` pattern (Lucide string -> component, falls back to
Grid2X2) and Home's openApp logic (internal -> setLocation, external
_tab -> window.open, external_iframe -> /embedded/:id). Renders
nothing when fewer than 1 other apps are available. Hidden in print,
respects safe-area-inset-bottom, scrollable on narrow widths.

Mounted on every app page except Home:
- executive-meetings.tsx (currentSlug="executive-meetings", matches
  seed slug; hidden when isFullscreen is true so the schedule fills
  the screen unobstructed).
- services.tsx (currentSlug="services")
- notes.tsx (currentSlug="notes")
- orders-incoming.tsx (currentSlug="orders-incoming" — not a built-in
  app slug, so all apps show, which is the right UX since incoming
  inbox is reached via the topbar, not the home grid).
- admin.tsx (currentSlug="admin")

Removed the two gear/back-arrow blocks added by #596 in
executive-meetings.tsx (test ids em-president-settings,
em-president-back-to-schedule) — the dock now provides cross-app
navigation including Admin/Settings. The L902 force-schedule effect
that snaps the president back to the schedule section outside of
schedule|settings is left in place; it still gates the hidden
Manage / Audit tabs.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No backend, no new translations, Home untouched.
2026-05-18 12:28:16 +00:00
riyadhafraa 5eeff7abaa #597 auto-bump deploy counter on every redeploy
scripts/update-version.mjs now maintains a `deployCount` integer and
rewrites `version` as `${baseVersion}.${deployCount}` every time the
build hash actually changes. When build is unchanged (re-run without
new commit) it still no-ops, matching prior behaviour, so re-running
the build twice in CI doesn't inflate the counter.

version.json shape:
  {
    "baseVersion": "0.1.0-dev",   <- stable, manually owned
    "version":     "0.1.0-dev.42", <- baseVersion + "." + deployCount
    "build":       "20260518.f942f101",
    "deployCount": 42
  }

Migration: on the very first run after this change, if `baseVersion`
is missing, the script derives it from the existing `version` field
by stripping any trailing `.<digits>`. If `deployCount` is missing it
seeds from that same trailing counter, so a repo that was already
hand-numbered (e.g. "0.1.0-dev.12") doesn't reset progress to 0.

Verified manually:
- Re-run with no new commit -> "build unchanged: ...", file untouched.
- New build -> bumps deployCount by exactly 1, rewrites version.
- Manually editing baseVersion to "0.2.0" -> next bump yields
  "0.2.0.<count+1>", manual base preserved.

api-server reads version.json statically (import with type: "json"),
so no API change is needed — the new fields flow through to
/api/system/version automatically. admin.tsx just prints
data.current.version, no regex parsing, so the new suffixed string
shows up in the System Updates card with no UI edit.

version.json reset to baseVersion=0.1.0-dev, version=0.1.0-dev,
deployCount=0 so the next legitimate Mac docker build bumps cleanly
to dev.1 on first redeploy.
2026-05-18 12:24:05 +00:00
riyadhafraa bca5025247 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 044e8744-eff4-4678-b723-9c212a488bbe
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
2026-05-18 12:22:31 +00:00
riyadhafraa c80b1ce085 #596 صلاحية الرئيس: حذف بحث الاجتماعات + زر الإعدادات
Two changes to artifacts/tx-os/src/pages/executive-meetings.tsx,
both gated on `isPresidentView` so non-president users are unaffected.

1. Schedule search hidden for the President
   - Wrapped <SearchToggle> in ScheduleSection (L2945) in
     {!isPresidentView && (...)}.
   - Kept searchQuery state + meetingMatchesSearch wiring intact:
     when query is "" the filter is a no-op, so the President sees
     the full day's meetings, and the box reappears for anyone else.

2. Gear button so the President can reach Settings (font controls)
   - Added an icon-only gear button (SettingsIcon, lucide-react —
     already imported) in the header right-side block (L1113),
     visible only when isPresidentView && section === "schedule".
     data-testid="em-president-settings".
   - Added an icon+label "back to schedule" button (ArrowRight for
     RTL / ArrowLeft for LTR) shown when isPresidentView &&
     section === "settings". data-testid="em-president-back-to-schedule".
   - Relaxed the force-schedule effect (L966) so the President is
     only redirected when section is neither "schedule" nor
     "settings" — otherwise the gear tap would bounce back instantly.
   - Sub-nav stays hidden (`!isPresidentView` on the SECTIONS map
     is unchanged). Gear is the only entry point.
   - No backend, no new i18n keys (reuses
     executiveMeetings.nav.settings / .schedule).
   - FontSettingsSection already renders for canRead users, and
     canEditGlobalFontSettings stays as the API reports it — no
     extra client-side gate.

`pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
2026-05-18 11:36:01 +00:00
riyadhafraa 599fc91e16 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 6ab1ba1d-35ad-4218-8cab-8d1f521c170a
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
2026-05-18 11:34:43 +00:00
riyadhafraa ccb891d4ca #594 meetings alert: replay chime after postpone
The user reported: when a 5-min meeting reminder fires and they hit
"Postpone 5 min", the second reminder appears at the new time but
**without sound** — and the only way to get the sound back is to leave
the app and come back (full tab reload).

Root cause: `UpcomingMeetingAlert` keeps a `playedRef: Set<number>` of
meeting ids that have already chimed, so the 30s polling/refetch
doesn't replay the chime over and over. The alert is mounted globally
in `App.tsx` and never unmounts during in-app navigation, so the Set
lives for the whole session. Postpone changes the meeting's
`startTime` but keeps the same `id`, so the second alert hit the
"already chimed" guard and stayed silent. Only a real tab reload
cleared the Set, which is exactly the workaround the user discovered.

Fix: change the dedupe key from `meeting.id` (number) to
`${meeting.id}:${startTime}` (string). Every postpone moves
startTime, so each post-postpone alert is treated as a fresh
instance and chimes again. The 3s throttle inside
`NotificationPlayer` still protects against burst-replay within the
same alert instance.

Untouched:
- `shownRef` — still keyed by id, because the server-side "shown"
  audit row is one-per-meeting-per-user by design.
- `NotificationPlayer`, iOS unlock path, vibration, throttle.
- Postpone API surface and `PostponeDialog`.

Verified: `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
2026-05-18 11:26:15 +00:00
riyadhafraa 1d1372257a Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4e3d27ac-1736-4209-bdbc-91c924239307
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
2026-05-18 11:25:15 +00:00
riyadhafraa 28547e2a7e Hide the centered clock on small screens to prevent overlap
Hide the absolutely positioned HeaderClock on mobile viewports to prevent it from overlapping with the header title and action buttons, restoring it on larger screens.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 425e9ff3-23ed-48ac-9baa-e63cfcba6efa
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
2026-05-18 11:20:21 +00:00
riyadhafraa cfba4fe9e9 #591 meetings: reuse the real notes Composer (not a custom textarea)
The yellow side tab on /executive-meetings previously opened a bespoke
textarea panel I added in #590. The user pointed out (screenshot) that
it should be the exact same composer card they use on /notes — yellow
background, color picker, label menu, checklist toggle, "حفظ" Save,
paper-plane Send.

Changes:
- Extracted Composer + its inline helpers (ChecklistEditor, KindToggle,
  ColorPicker, LabelMenu, newItemId) out of pages/notes.tsx into a new
  shared module at components/notes/composer.tsx.
- Extracted SendNoteDialog out of pages/notes.tsx into a new shared
  module at components/notes/send-note-dialog.tsx.
- pages/notes.tsx now imports both modules; behavior of the notes page
  is unchanged.
- pages/executive-meetings.tsx: deleted InlineQuickNotePanel. The
  schedule view now renders <Composer folderSelection={{kind:"all"}}>
  inside the yellow side-tab toggle, plus <SendNoteDialog> for the
  paper-plane Send flow. A new quickNoteTrigger counter is bumped each
  time the tab opens so the shared Composer auto-expands and focuses
  its textarea. A small X in the corner dismisses the panel; Save just
  collapses the composer (matches /notes UX), Send saves then opens
  the recipient picker.
- Notes created from the meetings panel land as unfiled notes in the
  user's notes (folder "all"), visible immediately in /notes.
- Composer gained an optional onClose prop fired from its reset()
  path (save / outside-click save / send success / empty reset).
  /notes doesn't pass it (composer stays mounted, just collapses).
  The meetings page wires it to setQuickNoteOpen(false) so the panel
  dismisses and the yellow side tab returns after every save path,
  not just the X / Send paths. Addresses code-review feedback.

Notes:
- No locale-key cleanup needed: the old quickNote.placeholder/send
  keys lived only as t() fallbacks, not in en.json/ar.json.
- pnpm --filter @workspace/tx-os exec tsc --noEmit: clean.
- HMR errors visible in the browser console were from the intermediate
  syntax-error state during extraction; cleared after the final edit.
2026-05-18 11:17:26 +00:00
riyadhafraa fe9ee04333 #591 meetings: reuse the real notes Composer (not a custom textarea)
The yellow side tab on /executive-meetings previously opened a bespoke
textarea panel I added in #590. The user pointed out (screenshot) that
it should be the exact same composer card they use on /notes — yellow
background, color picker, label menu, checklist toggle, "حفظ" Save,
paper-plane Send.

Changes:
- Extracted Composer + its inline helpers (ChecklistEditor, KindToggle,
  ColorPicker, LabelMenu, newItemId) out of pages/notes.tsx into a new
  shared module at components/notes/composer.tsx.
- Extracted SendNoteDialog out of pages/notes.tsx into a new shared
  module at components/notes/send-note-dialog.tsx.
- pages/notes.tsx now imports both modules; behavior of the notes page
  is unchanged.
- pages/executive-meetings.tsx: deleted InlineQuickNotePanel. The
  schedule view now renders <Composer folderSelection={{kind:"all"}}>
  inside the yellow side-tab toggle, plus <SendNoteDialog> for the
  paper-plane Send flow. A new quickNoteTrigger counter is bumped each
  time the tab opens so the shared Composer auto-expands and focuses
  its textarea. A small X in the corner dismisses the panel; Save just
  collapses the composer (matches /notes UX), Send saves then opens
  the recipient picker.
- Notes created from the meetings panel land as unfiled notes in the
  user's notes (folder "all"), visible immediately in /notes.

Notes:
- No locale-key cleanup needed: the old quickNote.placeholder/send
  keys lived only as t() fallbacks, not in en.json/ar.json.
- pnpm --filter @workspace/tx-os exec tsc --noEmit: clean.
- HMR errors visible in the browser console were from the intermediate
  syntax-error state during extraction; cleared after the final edit.
2026-05-18 11:15:22 +00:00
riyadhafraa be0d930807 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8b05f6be-bbfd-4a0e-bcce-b5b1d6fe854f
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
2026-05-18 11:09:51 +00:00
riyadhafraa bf81debebf 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
riyadhafraa 3cfa24afc9 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ab441ac4-930e-49c1-b806-7244fcac45fd
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
2026-05-18 11:00:50 +00:00
riyadhafraa c8b0edc13a 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.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4b47ebeb-8643-405e-b1c8-6d9320ea2345
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
2026-05-18 10:40:16 +00:00
riyadhafraa 1659dc4b68 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.b5efd9eb) 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.a34eb5f5). 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
riyadhafraa cddbbef20f Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 919cc635-e729-446a-9202-ab5bfd9a1125
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
2026-05-18 10:34:58 +00:00
riyadhafraa a34eb5f56d 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
riyadhafraa 0689f5986c 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
riyadhafraa a5cfa480e3 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0804c0ee-3e45-4cc9-862b-8c97ebbdf3f3
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/2CNQBAM
Replit-Helium-Checkpoint-Created: true
2026-05-18 10:24:02 +00:00
riyadhafraa b2f500500e 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 80429920.

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
riyadhafraa 7b94653f5e Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: dc143294-2521-4b08-9093-1630b81a6cf5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/2CNQBAM
Replit-Helium-Checkpoint-Created: true
2026-05-18 09:53:53 +00:00
riyadhafraa f26b6da4af 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
riyadhafraa 41a3525594 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 6ae5f392-47b5-479f-9487-5f91077090a7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk
Replit-Helium-Checkpoint-Created: true
2026-05-18 09:45:37 +00:00
riyadhafraa 15bec59724 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
riyadhafraa a487c1661d 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
riyadhafraa fda6a38128 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
riyadhafraa 224bf49783 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
riyadhafraa e296216c71 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
riyadhafraa f39d8edea2 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
riyadhafraa 0d0a729175 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b42989de-3752-456b-a773-c1ea46ddc038
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk
Replit-Helium-Checkpoint-Created: true
2026-05-18 09:19:46 +00:00
riyadhafraa 207bbb482c 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.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: fb6e8c2d-05ca-4eef-bd9b-dcf819bfcfcd
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk
Replit-Helium-Checkpoint-Created: true
2026-05-18 08:30:20 +00:00
riyadhafraa 7bb27527fb 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
riyadhafraa cc732b5a36 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2a0ec3ab-4f12-4c3f-bd60-50f5e96245bf
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk
Replit-Helium-Checkpoint-Created: true
2026-05-18 08:28:09 +00:00
riyadhafraa 3d3a4ba972 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