7bb27527fbc2f2a87704a861b41e78450458c389
417 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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.
|
||
|
|
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. |
||
|
|
d547d695e4 |
#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. |
||
|
|
aba984d238 |
Improve handling of notification permission prompts
Update notification permission logic to differentiate between denied and dismissed states, and adjust locale messages accordingly. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 02bc349c-c9f1-47f4-967c-cfefeabde8bb 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 |
||
|
|
558d395e0c |
#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.
|
||
|
|
192f412f2a |
#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
("إخفاؤها بشكل مدروس").
|
||
|
|
2f591a6ba8 |
Show the notifications app on the main page again
Remove hardcoded exclusion for the notifications app in the home page component. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 58af0916-ab79-457c-868e-3b72336f6067 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 |
||
|
|
b5efd9eb12 |
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. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 1e6de894-7c78-4557-b01a-a2c1c01f4155 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 |
||
|
|
191e072353 |
#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 |
||
|
|
2aa4a12ddc |
#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 |
||
|
|
c40384b0d9 |
#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. |
||
|
|
dc30d75e11 |
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. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: e1c6509c-638c-4c43-8d4f-4b4a5910fc4a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fNoqq4C Replit-Helium-Checkpoint-Created: true |
||
|
|
21dda3d372 |
#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).
|
||
|
|
27384d019a |
#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).
|
||
|
|
4b151c50a8 |
#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).
|
||
|
|
4c57c58f8c |
#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. |
||
|
|
4e5bed602e |
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). |
||
|
|
620ab284e2 |
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). |
||
|
|
51c3d8a8ba |
Fix toggle switch behavior in right-to-left reading modes
Update the switch component's CSS to correctly position the toggle thumb in RTL layouts for both checked and unchecked states. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ee6229a6-5472-4ca0-8f84-74b229703438 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
f4253d4d9d |
Improve toggle switch behavior in right-to-left languages
Add custom RTL and LTR variants to Tailwind CSS configuration to correctly position toggle switch indicators in Arabic and other right-to-left languages. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 84451adc-9c86-40d5-80ba-846e7ea2a813 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
bf39a6eda6 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Four new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe`, and a spec-aligned alias `DELETE /api/push/subscribe?endpoint=...` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`, gated on `serviceWorker` AND `PushManager` support so older browsers no-op cleanly. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - Architect rounds addressed in order: 1. race + payload size — fixed. 2. dedup gate + first-launch UX + SW base-path + tests — fixed. 3. DELETE alias + PushManager check — fixed in this commit. A true 410-cleanup test was attempted but reaching the prune branch from an out-of-process .mjs test requires mocking the `web-push` module, which the current `node --test` + bundled-dist harness doesn't support cleanly; the 404/410 catch branch is small, self-contained, and was code-reviewed. Tracked as a follow-up. |
||
|
|
8f3b750961 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Three new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - Two architect rounds: first flagged race + payload size (fixed), second flagged dedup gate + first-launch UX + SW base-path + tests (all fixed in this commit). |
||
|
|
243ccb3e00 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral in-memory. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes so over-sized notes don't kill delivery. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (account switch on shared device) so the previous user's notifications can't leak. - Three new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe` (auth-gated, Zod-validated). - Push hooked into the 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick handlers only (no asset caching). Focuses an existing tab or opens a new one at the payload URL. - SW registration in `main.tsx` scoped to `BASE_URL`. - `use-push-subscription` hook (enable/disable/refresh + status: unsupported / denied / default / subscribed). - New `PushToggleRow` in `NotificationSettingsContent` with ar/en strings (notifSettings.push.*). iPad copy explains that the user must add to Home Screen first for iOS to allow Web Push. Plumbing - OpenAPI: 3 new operations under `notifications` tag; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the generated key; subscribe endpoint 401s without auth as expected. - Architect review surfaced two HIGH issues (account-switch leak, payload size); both fixed before completion. |
||
|
|
e9d7b8dc76 |
Fix service images + Arabic ي dots on home tiles (Task #552)
Two visible bugs reported from the iPad after Task #551 deploy: 1) Only "قهوة سعودي" showed its image on /services. Three other seeded services (شاي، بلاك كوفي، عصير طازج) showed a broken-image placeholder because the seed only set `imageUrl` for Saudi Coffee. 2) The dots under final ي in the home-grid tile label "خدماتي" didn't render on iOS Safari. The label `<span>` didn't pin a font-family, so iOS fell back to a Latin face that lacks proper Arabic glyphs. Changes: - scripts/src/seed.ts: • Added `imageUrl` for tea/black-coffee/juice in the insert array (covers fresh installs). • Added a backfill loop AFTER the insert that fills `imageUrl` on existing rows only when the value is currently NULL. Admin- customized image URLs are never overwritten — matches the existing legacy-rename pattern at lines 395-423. - artifacts/tx-os/src/pages/home.tsx (AppIconContent label): • Added `lang` and `dir` attributes on the tile `<span>`. • Pinned an explicit font stack: DIN Next LT Arabic → Helvetica Neue LT Arabic → Tajawal for Arabic; Helvetica Neue → system-ui for English. The three Arabic faces are already declared in custom-fonts.css. No schema changes, no new dependencies. Verified typecheck passes and HMR reloaded both files cleanly in the dev server. Push to Gitea is a separate follow-up (git commit is restricted in the main agent; platform commits this task's changes on completion, and the push task can then mirror them to the Mac). |
||
|
|
7a2ae8434d |
Update project documentation and code comments to remove platform-specific references
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fa18e5d4-a810-4bd5-8cde-2a60d64d9e3f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu Replit-Helium-Checkpoint-Created: true |
||
|
|
3af737186d |
Improve admin modal design and accessibility with shared component
Refactors AdminFormDialog to create a reusable component for admin modals, including aria-labelledby/describedby attributes and Escape key closing functionality. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fa532550-4873-409b-840a-ac4c474132f3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu Replit-Helium-Checkpoint-Created: true |
||
|
|
a15365f459 |
Task #531: Redesign DeletionWarningDialog for a more professional look
Scope: artifacts/tx-os/src/pages/admin.tsx — the shared confirm
dialog used for deleting Apps, Services, and Users on the admin
page.
What changed
- Three explicit visual regions instead of a flat stack:
- Header: AlertTriangle icon in a destructive-tinted circle next
to the bold title (which already contains the item name from
the existing translation strings).
- Body: warning copy, then impacted items rendered inside a
sub-card (rounded border + subtle bg + custom bullet dots
instead of `list-disc`), then a destructive-tinted callout
holding the "cannot be undone" hint.
- Footer: separated by a top border and a faint bg tint;
Cancel = ghost (quieter), Confirm/Anyway = destructive (clear
primary action). When `isPending`, a small spinner appears
inside the destructive button.
- Slightly larger max-width (max-w-md), shadow-xl, and overflow-
hidden so the rounded corners read cleanly with the new footer
divider.
- `role="alertdialog"` + `aria-modal="true"` added on the panel.
Behaviour preserved
- Component props, call sites, and all `data-testid` hooks are
unchanged (testId, confirmTestId — covers app-delete-dialog/
-confirm, service-delete-dialog/-confirm, user-delete-dialog/
-confirm).
- No translation keys added; reuses every existing
admin.deleteApp/Service/User.* string.
- Cancel renders before Confirm in the DOM so tab order is
Cancel -> Destructive (no a11y regression). Visual order is
controlled with `justify-end`, which mirrors correctly in RTL.
- Pure UI change: no API/handler/permission logic touched.
Notes
- Architect flagged an earlier `flex-row-reverse` footer for
putting the destructive button first in tab order; fixed before
marking complete.
- Pre-existing TS18046 / TS2345 errors in admin.tsx are unrelated.
|
||
|
|
21ccb6c426 |
Task #531: Redesign DeletionWarningDialog for a more professional look
Scope: artifacts/tx-os/src/pages/admin.tsx — the shared confirm
dialog used for deleting Apps, Services, and Users on the admin
page.
What changed
- Three explicit visual regions instead of a flat stack:
- Header: AlertTriangle icon in a destructive-tinted circle next
to the bold title (which already contains the item name from
the existing translation strings).
- Body: warning copy, then impacted items rendered inside a
sub-card (rounded border + subtle bg + custom bullet dots
instead of `list-disc`), then a destructive-tinted callout
holding the "cannot be undone" hint.
- Footer: separated by a top border and a faint bg tint;
Cancel = ghost (quieter), Confirm/Anyway = destructive (clear
primary action). When `isPending`, a small spinner appears
inside the destructive button.
- Slightly larger max-width (max-w-md), shadow-xl, and overflow-
hidden so the rounded corners read cleanly with the new footer
divider.
- `role="alertdialog"` + `aria-modal="true"` added on the panel.
Behaviour preserved
- Component props, call sites, and all `data-testid` hooks are
unchanged (testId, confirmTestId — covers app-delete-dialog/
-confirm, service-delete-dialog/-confirm, user-delete-dialog/
-confirm).
- No translation keys added; reuses every existing
admin.deleteApp/Service/User.* string.
- Cancel renders before Confirm in the DOM so tab order is
Cancel -> Destructive (no a11y regression). Visual order is
controlled with `justify-end`, which mirrors correctly in RTL.
- Pure UI change: no API/handler/permission logic touched.
Notes
- Architect flagged an earlier `flex-row-reverse` footer for
putting the destructive button first in tab order; fixed before
marking complete.
- Pre-existing TS18046 / TS2345 errors in admin.tsx are unrelated.
|
||
|
|
60fbd3dd84 |
Task #529: Restore topbar logout + collapsible Settings groups
home.tsx - Re-import LogOut from lucide-react. - Append a single-tap Logout icon button after <SettingsPanel /> in the right-side cluster, wired to the existing handleLogout flow. - Drop the onLogout prop from <SettingsPanel />; update the topbar comment block to describe the Bell -> Inbox -> Settings -> Logout cluster. settings-panel.tsx - Drop the LogOut import, the onLogout prop on SettingsPanel and SettingsPanelBody, the local handleLogout wrapper, and the inline logout button (data-testid="settings-logout"). - Wrap the four groups (language, notifications, sound, clock) in the existing shadcn Accordion (type="multiple") so each toggles independently. Default = all collapsed; open state is lifted to SettingsPanel via useState<GroupId[]> so toggles persist while the panel is closed and re-opened in the same session. - New GroupItem helper styles each AccordionItem like the prior GroupCard so the panel keeps its rounded-card visual rhythm. The trigger uses text-start so headers align correctly in RTL. - Existing form bodies (LanguageBody, NotificationSettingsContent, SoundBody, ClockStyleContent) are reused unchanged, preserving all DB + localStorage persistence. Notes - Logout placement and removal from the panel were both explicit user requests (clarified in plan #529 after #527 unified the topbar). - No new i18n keys; existing settingsPanel.section.* labels reused as accordion triggers. - typecheck clean (pre-existing TS6305/TS7006 noise unrelated). |
||
|
|
7abb0c870b |
Task #527: unify topbar into per-user Settings panel
Topbar right cluster collapses from 5–7 icons to exactly Bell -> Inbox
(conditional on canReceiveOrders) -> Settings (gear). Logout moved
inside the Settings panel; the second bell, globe, volume and clock
buttons are gone from the topbar.
New SettingsPanel renders as a Popover on md+ and a bottom Sheet on
<md (via new useMediaQuery hook). It composes existing controls
rather than re-implementing them:
- Language radio uses useUpdateLanguage + i18n.changeLanguage
- Notifications group renders extracted NotificationSettingsContent
- Sound group toggles notificationsMuted (same flag QuickMute used)
- Clock group renders extracted ClockStyleContent
All DB + localStorage persistence is preserved unchanged.
Refactors:
- notification-settings.tsx: extracted NotificationSettingsContent;
NotificationSettingsPicker now wraps it (kept for any future caller).
- clock-style-picker.tsx: extracted ClockStyleContent; ClockStylePicker
now wraps it; removed dead setOpen reference inside choose().
- home.tsx: removed Globe / QuickMute / NotificationSettingsPicker /
ClockStylePicker / standalone LogOut from the cluster, dropped
related imports + useUpdateLanguage, reordered to Bell -> Inbox ->
Settings, and passes onLogout to SettingsPanel.
i18n: added settingsPanel.{title, section.*, lang.*, sound.master}
keys to ar.json and en.json.
Code review: PASS.
|
||
|
|
e6a3b148f4 |
Task #527: unify topbar into per-user Settings panel
Topbar right cluster collapses from 5–7 icons to exactly Bell -> Inbox
(conditional on canReceiveOrders) -> Settings (gear). Logout moved
inside the Settings panel; the second bell, globe, volume and clock
buttons are gone from the topbar.
New SettingsPanel renders as a Popover on md+ and a bottom Sheet on
<md (via new useMediaQuery hook). It composes existing controls
rather than re-implementing them:
- Language radio uses useUpdateLanguage + i18n.changeLanguage
- Notifications group renders extracted NotificationSettingsContent
- Sound group toggles notificationsMuted (same flag QuickMute used)
- Clock group renders extracted ClockStyleContent
All DB + localStorage persistence is preserved unchanged.
Refactors:
- notification-settings.tsx: extracted NotificationSettingsContent;
NotificationSettingsPicker now wraps it (kept for any future caller).
- clock-style-picker.tsx: extracted ClockStyleContent; ClockStylePicker
now wraps it; removed dead setOpen reference inside choose().
- home.tsx: removed Globe / QuickMute / NotificationSettingsPicker /
ClockStylePicker / standalone LogOut from the cluster, dropped
related imports + useUpdateLanguage, reordered to Bell -> Inbox ->
Settings, and passes onLogout to SettingsPanel.
i18n: added settingsPanel.{title, section.*, lang.*, sound.master}
keys to ar.json and en.json.
Code review: PASS.
|
||
|
|
04f2ab7ea9 |
Task #527: Unify topbar prefs into per-user Settings panel
- New `settings-panel.tsx` consolidates four duplicate topbar buttons
(clock-style picker, quick-mute, notification-settings popover,
language-toggle globe) behind a single gear icon that opens a
right-side Sheet (full-width on mobile, capped on desktop).
- Panel groups: Language (ar/en), Notifications (per-slot
enabled/sound/vibration), Sound (master mute), Clock (visibility,
12/24h, style).
- Reuses existing hooks (useUpdateLanguage, useUpdate{Clock*},
useUpdatePrefs, useHome/TopbarClockVisibility) so all persistence
paths (DB columns + localStorage) stay identical — no migration.
- home.tsx topbar now renders only inbox link, single notification
bell (kept as nav link), Settings gear, and logout.
- New i18n keys under `settingsPanel.*` in ar.json + en.json.
- Old ClockStylePicker / NotificationSettingsPicker / QuickMuteButton
exports remain in their original files (unused) — kept to minimize
diff scope; can be removed in a follow-up.
|
||
|
|
d2b4f82736 |
Task #521: shorten meetings page URL to /meetings
- artifacts/tx-os/src/App.tsx: serve the schedule at /meetings; add an ExecutiveMeetingsRedirect that rewrites /executive-meetings (and any /executive-meetings/* sub-path) to /meetings while preserving the query string and hash, so existing PDFs, emails, and bookmarks keep working with one transparent hop. - scripts/src/seed.ts: seed the apps row with route="/meetings" and update the expectedBuiltinRoutes drift guard to match. Add an idempotent UPDATE-by-slug after the apps insert so already-deployed rows (including any drift like /executive-meetings or /mms) get reconciled to /meetings on the next seed — safe because the slug is in BUILTIN_APP_SLUGS, meaning the SPA owns the route. - artifacts/tx-os/tests/meetings-route-redirect.spec.mjs: new spec verifying that /meetings loads, /executive-meetings?date=...#... is redirected with query+hash intact, and /api/apps now reports route="/meetings" for the executive-meetings slug. Out of scope (intentionally left untouched per task): API paths under /api/executive-meetings/*, React Query keys, DB tables, the apps slug "executive-meetings", page/component file names, the "Meetings" display name, and the executive_meetings:* permission name. |
||
|
|
bfbf59cb20 |
Task #520: skip send-note confirm when only one recipient
The send-note composer always showed an AlertDialog before posting, even for the common one-on-one case. The dialog now only appears when two or more recipients are picked. Implementation (artifacts/tx-os/src/pages/notes.tsx): - Extracted shared `performSend()` from `confirmSubmit` so both the direct-send and confirm-then-send paths reuse the same mutation, success toast + composer close, and error toast. - `requestSubmit()` now branches: 0 picked = no-op, 1 picked = call performSend() directly, 2+ picked = open the existing AlertDialog. - `confirmSubmit()` (the AlertDialog action) delegates to performSend. - No changes to API, recipient picker, AR/EN strings, or any other AlertDialog (bulk archive/delete left untouched). Tests: - Updated artifacts/tx-os/tests/notes-inbox.spec.mjs (sends to a single recipient): no longer expects the confirm dialog; asserts /api/notes/:id/send fires directly and the composer closes. - New artifacts/tx-os/tests/notes-send-confirm-threshold.spec.mjs: - "single-recipient send skips the confirm dialog" (passes) - "multi-recipient send still requires the confirm dialog" (passes) Validation: tx-os tsc clean; both threshold specs pass locally; architect review APPROVED. |
||
|
|
9d60b35955 |
Task #520: skip send-note confirm when only one recipient
The send-note composer always showed an AlertDialog before posting, even for the common one-on-one case. The dialog now only appears when two or more recipients are picked. Implementation (artifacts/tx-os/src/pages/notes.tsx): - Extracted shared `performSend()` from `confirmSubmit` so both the direct-send and confirm-then-send paths reuse the same mutation, success toast + composer close, and error toast. - `requestSubmit()` now branches: 0 picked = no-op, 1 picked = call performSend() directly, 2+ picked = open the existing AlertDialog. - `confirmSubmit()` (the AlertDialog action) delegates to performSend. - No changes to API, recipient picker, AR/EN strings, or any other AlertDialog (bulk archive/delete left untouched). Tests: - Updated artifacts/tx-os/tests/notes-inbox.spec.mjs (sends to a single recipient): no longer expects the confirm dialog; asserts /api/notes/:id/send fires directly and the composer closes. - New artifacts/tx-os/tests/notes-send-confirm-threshold.spec.mjs: - "single-recipient send skips the confirm dialog" (passes) - "multi-recipient send still requires the confirm dialog" (passes) Validation: tx-os tsc clean; both threshold specs pass locally; architect review APPROVED. |
||
|
|
6a01ce852a |
Task #517: App image upload, external links, built-in route lock
Admin Add/Edit App now supports: - Custom image upload (or fall back to Lucide icon) via the existing ServiceImageUploader; rendered on the home launcher when set. - Open mode picker: internal (default), external_tab (window.open), external_iframe (renders inside /embedded/:id). External URL input shown conditionally and required when an external mode is chosen. - Internal route field is hidden entirely when an external mode is selected, and locked (readOnly + lock hint) when editing a built-in app. Slug input is also locked (readOnly) for built-in apps so the built-in identity cannot drift via the form. Backend: - apps schema gains image_url, external_url, open_mode (default 'internal'); drizzle-kit push applied. - New lib/db/src/built-in-apps.ts exports BUILTIN_APP_SLUGS + isBuiltinAppSlug. Exposed via subpath export `@workspace/db/built-in-apps`; the file has zero imports so the browser bundle uses it without pulling in `pg`. tx-os now imports it directly — duplicate FE constant removed. - Built-in slug list: services, notifications, admin, notes, my-orders, orders-incoming, executive-meetings (everything with a hardcoded <Route> in artifacts/tx-os/src/App.tsx). calendar / documents are seeded but admin-defined and remain editable. - PATCH /apps/:id rejects route changes whose previous slug is built-in with 400 + code='builtin_route_locked'. Same-route no-op is allowed; non-route updates on built-ins still work. - PATCH /apps/:id ALSO rejects slug changes when the previous slug is built-in (code='builtin_slug_locked'). UpdateAppBody zod schema intentionally omits slug, so we inspect req.body.slug raw before zod stripping. Closes the 2-step bypass: rename slug (allowed) → change route (now previous.slug looks non-built-in, allowed). - POST /apps and PATCH /apps/:id reject externalUrl values that are not http:// or https:// (code='invalid_external_url'). Prevents shipping javascript:/data:/file: payloads tenant-wide via launcher. Other: - New SPA route /embedded/:id and embedded-app page (iframe host with back + open-in-new-tab + error/not-embeddable states). - OpenAPI App / CreateAppBody / UpdateAppBody extended; codegen ran. - en/ar locales: admin.appImage, appExternalUrl, appOpenMode.*, builtinPathLocked, embeddedFrame.*. - scripts/src/seed.ts: drift guard throws if a seeded built-in slug uses a route that does not match the hardcoded SPA route. Tests: - New API test apps-builtin-route-lock.test.mjs (6/6 pass): reject built-in route change, allow non-route built-in updates, allow non-builtin route changes, reject built-in slug change (anti-bypass), reject non-http(s) externalUrl scheme + accept https, allow built-in same-route no-op. - New Playwright E2E admin-app-image-external-embedded.spec.mjs (passes): launcher renders custom image_url, external_tab opens external URL via window.open, external_iframe navigates to /embedded/:id and renders <iframe src=externalUrl>. Out of scope (pre-existing, not introduced here): - Failing tests in executive-meetings-* and tsc errors in api-server/src/routes/executive-meetings.ts. |
||
|
|
e7948012f4 |
Task #517: App image upload, external links, built-in route lock
Admin Add/Edit App now supports: - Custom image upload (or fall back to Lucide icon) via the existing ServiceImageUploader; rendered on the home launcher when set. - Open mode picker: internal (default), external_tab (window.open), external_iframe (renders inside /embedded/:id). External URL input shown conditionally and required when an external mode is chosen. Internal route field is hidden entirely when an external mode is selected, and locked (readOnly + lock hint) when editing a built-in app whose path is hardcoded in the SPA. Backend: - apps schema gains image_url, external_url, open_mode (default 'internal'); drizzle-kit push applied. - New lib/db/src/built-in-apps.ts exports BUILTIN_APP_SLUGS + isBuiltinAppSlug. Exposed via subpath export `@workspace/db/built-in-apps`; the file has zero imports so the browser bundle can use it without pulling in `pg`. tx-os now imports it directly — duplicate FE constant removed. - Built-in slug list: services, notifications, admin, notes, my-orders, orders-incoming, executive-meetings (everything with a hardcoded <Route> in artifacts/tx-os/src/App.tsx). calendar / documents are seeded but admin-defined and remain editable. - PATCH /apps/:id rejects route changes whose previous slug is built-in with 400 + code='builtin_route_locked'. Same-route no-op is allowed; non-route updates on built-ins still work. Other: - New SPA route /embedded/:id and embedded-app page (iframe host with back + open-in-new-tab + error/not-embeddable states). - OpenAPI App / CreateAppBody / UpdateAppBody extended; codegen ran. - en/ar locales: admin.appImage, appExternalUrl, appOpenMode.*, builtinPathLocked, embeddedFrame.*. - scripts/src/seed.ts: drift guard throws if a seeded built-in slug uses a route that does not match the hardcoded SPA route. Tests: - New API test apps-builtin-route-lock.test.mjs (4/4 pass): reject built-in route change, allow non-route built-in updates, allow non-builtin route changes, allow built-in same-route no-op. - New Playwright E2E admin-app-image-external-embedded.spec.mjs (passes): launcher renders custom image_url, external_tab opens external URL via window.open, external_iframe navigates to /embedded/:id and renders <iframe src=externalUrl>. Out of scope (pre-existing, not introduced here): - 3 failing tests in executive-meetings-* and tsc errors in api-server/src/routes/executive-meetings.ts. |
||
|
|
a794f92e61 |
Task #517: App image upload, external links, built-in route lock
Admin Add/Edit App now supports: - Custom image upload (or fall back to Lucide icon) via the existing ServiceImageUploader; rendered on the home launcher when set. - Open mode picker: internal (default), external_tab (window.open), external_iframe (renders inside /embedded/:id). External URL input shown conditionally and required by the form when an external mode is chosen. - Route field is locked (readOnly + lock hint) when editing a built-in app, since those slugs are hardcoded in the SPA router. Backend: - apps schema gains image_url, external_url, open_mode (default 'internal'); drizzle-kit push applied. - New lib/db/src/built-in-apps.ts exports BUILTIN_APP_SLUGS + isBuiltinAppSlug, re-exported from lib/db. - PATCH /apps/:id rejects route changes whose previous slug is built-in with 400 + code='builtin_route_locked'. Same-route no-op is allowed; non-route updates on built-ins still work. Other: - New SPA route /embedded/:id and embedded-app page (iframe host with back + open-in-new-tab + error/not-embeddable states). - OpenAPI App / CreateAppBody / UpdateAppBody extended; codegen ran. - en/ar locales: admin.appImage, appExternalUrl, appOpenMode.*, builtinPathLocked, embeddedFrame.*. - New tests in apps-builtin-route-lock.test.mjs (4/4 pass) covering reject built-in route change, allow non-route built-in updates, allow non-builtin route changes, allow built-in same-route no-op. Notes / drift: - BUILTIN_APP_SLUGS is duplicated inline in admin.tsx (BUILTIN_APP_SLUGS_FE) because the browser bundle cannot import @workspace/db (pulls pg). Comment points at the canonical source; drift risk filed as a follow-up. - Pre-existing failures unrelated to this task: 3 tests in executive-meetings-* and tsc errors in api-server/src/routes/executive-meetings.ts. Out of scope. |
||
|
|
f62969dc53 |
Task #515: Reply to a note from the popup without leaving the page
The floating "new note" popup's Reply button used to navigate to /notes?thread=X&reply=1, yanking the user out of whatever page they were on (Tasks, Calendar, etc.). Now Reply opens an inline composer inside the popup card itself. Changes (artifacts/tx-os/src/components/notes/incoming-note-popup.tsx): - New composerOpen / replyText / justSent state, reset whenever the popup head rotates so a half-typed reply never leaks across payloads. - handleReply now opens the inline composer and focuses the textarea (rAF) instead of routing. Acknowledge (mark-as-read) is deferred to successful send so Cancel keeps the note unread. - handleSubmitReply uses the existing useReplyToNote hook. Resolves recipientUserId from replyPayload.replier.id for the reply variant (owner replying back), undefined for the note variant (recipient replying — server infers the original sender). - On success: brief on-card "Reply sent" confirmation (aria-live), then auto-dismiss after 700ms. On failure: destructive toast with the textarea preserved. - Composer textarea: Enter (or Cmd/Ctrl+Enter) sends, Shift+Enter inserts a newline. Esc cancels the composer and is handled by the popup's global keydown listener so it works whether focus is in the textarea or on the Send/Cancel buttons. dir="auto" so the user's reply renders RTL/LTR per its own characters. - pointerdown/click stopPropagation on the composer wrapper so typing doesn't accidentally drag the card. Locale keys added to ar.json + en.json under notes.popup: inlineReplyPlaceholder, sendReply, cancelReply, replySending, replySent, replyFailed. Pre-existing failing tests (groups-crud, executive-meetings- notifications, executive-meetings-postpone-race) are unrelated to this change. |
||
|
|
a55fb16c4c |
Task #515: Reply to a note from the popup without leaving the page
The floating "new note" popup's Reply button used to navigate to /notes?thread=X&reply=1, yanking the user out of whatever page they were on (Tasks, Calendar, etc.). Now Reply opens an inline composer inside the popup card itself. Changes (artifacts/tx-os/src/components/notes/incoming-note-popup.tsx): - New composerOpen / replyText / justSent state, reset whenever the popup head rotates so a half-typed reply never leaks across payloads. - handleReply now opens the inline composer and focuses the textarea (rAF) instead of routing. Acknowledge (mark-as-read) is deferred to successful send so Cancel keeps the note unread. - handleSubmitReply uses the existing useReplyToNote hook. Resolves recipientUserId from replyPayload.replier.id for the reply variant (owner replying back), undefined for the note variant (recipient replying — server infers the original sender). - On success: brief on-card "Reply sent" confirmation (aria-live), then auto-dismiss after 700ms. On failure: destructive toast with the textarea preserved. - Composer textarea: Cmd/Ctrl+Enter to send, plain Enter for newline, Esc to cancel (stops propagation so the global Esc listener doesn't dismiss the whole popup with an unsent reply). dir="auto" so the user's reply renders RTL/LTR per its own characters. - pointerdown/click stopPropagation on the composer wrapper so typing doesn't accidentally drag the card. Locale keys added to ar.json + en.json under notes.popup: inlineReplyPlaceholder, sendReply, cancelReply, replySending, replySent, replyFailed. Pre-existing failing tests (groups-crud, executive-meetings- notifications, executive-meetings-postpone-race) are unrelated to this change. |
||
|
|
b7b8c395f4 |
Task #514: Align note composer with folders rail
Original task: On the Notes page the "اكتب ملاحظة" composer sat alone in a full-width row above the content row, while the "المجلدات" FoldersRail started in the row below. Composer appeared high, rail visibly lower with empty space between them. Change: - Switched the folders+content row in `artifacts/tx-os/src/pages/notes.tsx` from flex to CSS grid: `grid md:grid-cols-[14rem_minmax(0,1fr)]`. Composer is placed in col 2 row 1, FoldersRail in col 1 (row-span-2 when composer is shown), content in col 2 row 2. Result: composer's top edge aligns with the rail's top edge on desktop and the empty gap is gone. - On mobile (`grid-cols-1`), `order-1 / order-2 / order-3` give the exact original mobile reading order: composer → rail → content. - Added a `className?: string` prop to `FoldersRail` (`artifacts/tx-os/src/components/notes/folders-rail.tsx`) appended to its root `<aside>`, so the page can pass grid placement classes. - Hidden-composer views (Inbox / Sent / Archived / Shared-with-me) collapse cleanly via conditional `md:row-span-2` on the rail and conditional `md:row-start-1 / md:row-start-2` on the content column — desktop becomes a simple two-cell row and mobile stays rail → content, matching the original behavior. - Grid-cols template, `md:col-start-2` on composer, and `md:col-start-2`/`md:row-start-*` on the content column are all GATED on `showFolders`. In views where the folders rail is hidden (Received/Inbox, Sent), the wrapper is a single-column grid with no left gutter and content takes the full width — preserving the original full-width layout for those views. Behavior preserved: - `showTopComposer` gating unchanged. - `data-testid="notes-top-composer"` preserved. - FoldersRail's existing internal classes unchanged; new className prop is purely additive. - Mobile reading order in active/non-shared view: composer → rail → content (same as before this task, when composer was a standalone block above the row). - Mobile reading order in hidden-composer views: rail → content (unchanged). Verification: `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes. No test files reference the composer block by structural selectors that the move would break. No deviations from the plan. |
||
|
|
cb6b067386 |
Task #514: Align note composer with folders rail
Original task: On the Notes page the "اكتب ملاحظة" composer sat alone in a full-width row above the content row, while the "المجلدات" FoldersRail started in the row below. Composer appeared high, rail visibly lower with empty space between them. Change: - Switched the folders+content row in `artifacts/tx-os/src/pages/notes.tsx` from flex to CSS grid: `grid md:grid-cols-[14rem_minmax(0,1fr)]`. Composer is placed in col 2 row 1, FoldersRail in col 1 (row-span-2 when composer is shown), content in col 2 row 2. Result: composer's top edge aligns with the rail's top edge on desktop and the empty gap is gone. - On mobile (`grid-cols-1`), `order-1 / order-2 / order-3` give the exact original mobile reading order: composer → rail → content. - Added a `className?: string` prop to `FoldersRail` (`artifacts/tx-os/src/components/notes/folders-rail.tsx`) appended to its root `<aside>`, so the page can pass grid placement classes. - Hidden-composer views (Inbox / Sent / Archived / Shared-with-me) collapse cleanly via conditional `md:row-span-2` on the rail and conditional `md:row-start-1 / md:row-start-2` on the content column — desktop becomes a simple two-cell row and mobile stays rail → content, matching the original behavior. Behavior preserved: - `showTopComposer` gating unchanged. - `data-testid="notes-top-composer"` preserved. - FoldersRail's existing internal classes unchanged; new className prop is purely additive. - Mobile reading order in active/non-shared view: composer → rail → content (same as before this task, when composer was a standalone block above the row). - Mobile reading order in hidden-composer views: rail → content (unchanged). Verification: `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes. No test files reference the composer block by structural selectors that the move would break. No deviations from the plan. |
||
|
|
d1875141aa |
Task #514: Align note composer with folders rail
Original task: On the Notes page the "اكتب ملاحظة" composer sat alone in a full-width row above the content row, while the "المجلدات" FoldersRail started in the row below. Composer appeared high, rail visibly lower with empty space between them. Change: - Moved the top composer out of its standalone full-width block and into the left content column (`flex-1 min-w-0`) of the existing folders+content row in `artifacts/tx-os/src/pages/notes.tsx`. Both now share the same row, so the composer's top edge aligns with the rail's top edge on desktop. - Added a `className?: string` prop to `FoldersRail` (`artifacts/tx-os/src/components/notes/folders-rail.tsx`) appended to its root <aside>, so the page can pass `order-*` classes. - On narrow widths (`flex-col`) the composer column gets `order-1 md:order-2` and the rail gets `order-2 md:order-1` ONLY when `showTopComposer` is true. In hidden-composer views (Inbox / Sent / Archived / Shared-with-me) no order classes are applied, so the rail/content stacking order on mobile stays exactly as it was before this task. Result: composer stacks ABOVE the rail on mobile in My Notes (preserving the original mobile reading order) and sits beside it on md+, with zero behavior change in the other views. Behavior preserved: - `showTopComposer` gating unchanged — still hidden on Inbox / Sent / Archived / Shared-with-me. - `data-testid="notes-top-composer"` preserved. - FoldersRail's existing width/scroll classes unchanged; new className is purely additive. - Stale comment about composer being "above the folders area" updated to describe the new in-column placement and the order-* mobile behavior. Verification: `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes. No test files reference the composer block by structural selectors that the move would break. No deviations from the plan. |
||
|
|
944a894a88 |
Task #514: Align note composer with folders rail
Original task: On the Notes page the "اكتب ملاحظة" composer sat alone in a full-width row above the content row, while the "المجلدات" FoldersRail started in the row below. Composer appeared high, rail visibly lower with empty space between them. Change: - Moved the top composer out of its standalone full-width block and into the left content column (`flex-1 min-w-0`) of the existing folders+content row in `artifacts/tx-os/src/pages/notes.tsx`. Both now share the same row, so the composer's top edge aligns with the rail's top edge on desktop. - Added a `className?: string` prop to `FoldersRail` (`artifacts/tx-os/src/components/notes/folders-rail.tsx`) appended to its root <aside>, so the page can pass `order-*` classes. - On narrow widths (`flex-col`) the composer column gets `order-1 md:order-2` and the rail gets `order-2 md:order-1`. Result: composer stacks ABOVE the rail on mobile (preserving the original mobile reading order) and sits beside it on md+. Behavior preserved: - `showTopComposer` gating unchanged — still hidden on Inbox / Sent / Archived / Shared-with-me. - `data-testid="notes-top-composer"` preserved. - FoldersRail's existing width/scroll classes unchanged; new className is purely additive. - Stale comment about composer being "above the folders area" updated to describe the new in-column placement and the order-* mobile behavior. Verification: `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes. No test files reference the composer block by structural selectors that the move would break. No deviations from the plan. |
||
|
|
037d530471 |
Task #514: Align note composer with folders rail
Original task: On the Notes page, the "اكتب ملاحظة" composer sat alone in a full-width row above the content row, while the "المجلدات" FoldersRail started in the row below it. The result was a visible vertical mismatch — composer high, rail noticeably lower with empty space between them. Change: Moved the top composer out of its standalone full-width block and into the left content column (`flex-1 min-w-0`) at the top of the existing row that already hosts the FoldersRail. Both now share the same row, so the composer's top edge aligns with the rail's top edge. Details: - Single edit in `artifacts/tx-os/src/pages/notes.tsx`. - The composer's `data-testid="notes-top-composer"` is preserved, so any existing E2E selectors continue to work. - `showTopComposer` gating (hide on Inbox/Sent/Archived/Shared-with-me) is unchanged — only the position of the rendered block moved. - Removed the old wrapper's `px-4 pt-1 max-w-6xl w-full mx-auto` (those paddings/centering are now provided by the parent row), and added a `mb-4` for breathing room between composer and the content below it inside the column. - Responsive behavior preserved: the parent row is `flex-col md:flex-row`, so on narrow widths the composer still stacks above the folders area cleanly with no horizontal overflow. Verification: pnpm tsc --noEmit on tx-os passes; no test files reference the composer block by structural selectors that the move would break. No deviations from the plan. |
||
|
|
9a8dc3175f |
Task #512: Per-recipient Delete in Notes Inbox
Adds a recipient-scoped delete that's distinct from Archive: a recipient
can remove a note from their own inbox without touching the underlying
note or other recipients' rows.
Backend (artifacts/api-server/src/routes/notes.ts):
- DELETE /notes/received/:id — recipient-only; 404 if no row.
- POST /notes/received/bulk-delete — body {ids:number[]}, max 500,
single SQL DELETE, returns {ok, notFound} for partial-success UI.
- Both registered before DELETE /notes/:id so Express matches /received
first.
Frontend (artifacts/tx-os):
- New hooks useDeleteReceivedNote / useBulkDeleteReceivedNotes in
src/lib/notes-api.ts; both invalidate notes + folders queries.
- Inbox bulk bar: Delete button (rose) next to Archive plus a confirm
AlertDialog with all/none/partial toasts.
- Inbox card: per-row Trash button next to the status badge with a
page-level confirm AlertDialog (testid inbox-row-delete-confirm).
- ThreadDialog: per-row Delete next to Archive plus a confirm dialog
that closes the thread on success.
- AR + EN locale strings added for all new copy.
Tests:
- artifacts/api-server/tests/notes-inbox-delete.test.mjs — recipient
delete, non-recipient/sender 404, bulk mix of valid+missing ids, and
DB-level checks that the note + other recipients survive.
- artifacts/tx-os/tests/notes-inbox-bulk-delete.spec.mjs — Playwright
flow seeds three received notes, bulk-deletes two from the inbox,
then deletes the third via the per-row card Delete button.
|
||
|
|
0988585c65 |
Task #512: Per-recipient Delete in Notes Inbox
Adds a recipient-scoped delete that's distinct from Archive: a recipient
can remove a note from their own inbox without touching the underlying
note or other recipients' rows.
Backend (artifacts/api-server/src/routes/notes.ts):
- DELETE /notes/received/:id — recipient-only; 404 if no row.
- POST /notes/received/bulk-delete — body {ids:number[]}, max 500,
single SQL DELETE, returns {ok, notFound} for partial-success UI.
- Both registered before DELETE /notes/:id so Express matches /received
first.
Frontend (artifacts/tx-os):
- New hooks useDeleteReceivedNote / useBulkDeleteReceivedNotes in
src/lib/notes-api.ts; both invalidate notes + folders queries.
- Inbox bulk bar gets a Delete button (rose) next to Archive plus a
confirm AlertDialog with all/none/partial toasts (src/pages/notes.tsx).
- ThreadDialog gets a per-row Delete next to Archive plus a single
confirm dialog that closes the thread on success.
- AR + EN locale strings added for all new copy.
Tests:
- artifacts/api-server/tests/notes-inbox-delete.test.mjs — recipient
delete, non-recipient/sender 404, bulk mix of valid+missing ids, and
DB-level checks that the note + other recipients survive.
- artifacts/tx-os/tests/notes-inbox-bulk-delete.spec.mjs — Playwright
flow seeds three received notes, bulk-deletes two from the inbox,
then deletes the third via ThreadDialog per-row.
|
||
|
|
84398de390 |
Task #511: Fully remove Chat feature
Destructive removal per user confirmation ("حذف نهائي ما يرجع").
Removed:
- API: routes/conversations.ts, schema/conversations.ts, all chat
socket handlers in src/index.ts, /admin/users/:id/dependents/
conversations+messages endpoints, conversation/message dependency
counts in users/stats routes.
- Web: pages/chat.tsx, /chat route, dock chat filter, MessageSquare
icon and messages StatCard on home, all chat-related UI in
notifications + admin (dependency badges, delete-dialog rows,
UserDependentConversations/Messages sections, count map keys).
- Locales: nav.chat, home.stats.messages, full chat.* block,
admin.deleteUser conv/msgCount, admin.users.counts.conv/msg,
admin.audit.unit.conversation_*/message_*, admin.dependents.user*.
- OpenAPI spec: tags, all /conversations/* paths, conv/msg dependent
paths, related schemas (ConversationWithDetails, MessageWithSender,
UserDependentConversation/MessageItem+Page, etc.), UserProfile and
UserDeletionConflict conv/msg fields, HomeStats.unreadMessages.
Regenerated client via orval.
- Database: dropped message_reads, messages,
conversation_participants, conversations (CASCADE); deleted
notifications with related_type='conversation' or type='chat';
deleted apps row with slug='chat'; ran drizzle push-force.
- Seed: removed chat:access permission + user-role assignment +
seeded chat app entry from scripts/src/seed.ts.
- Tests: deleted conversations-leave.test.mjs; cleaned chat refs from
list-dependency-counts, delete-force-warnings, audit-log-coverage,
and admin-inline-dependency-counts (e2e) — replaced chat dependents
with note dependents where needed for force-delete coverage.
Notes preserved: notes.tsx noConversationsYet/conversationWith refer
to NOTE THREADS (not chat) and were intentionally NOT touched.
executive-meetings.ts not modified per replit.md restriction.
Pre-existing flaky test failures in executive-meetings/group/etc
suites remain unrelated to this task.
|
||
|
|
992f98418c |
Add ability to archive multiple incoming notes at once
Introduces a bulk archive feature for the received notes tab, including API integration, UI selection state, confirmation dialogs, and internationalization support. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7c13c05c-e8c2-461e-a5dc-94bbc8e114ac Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv Replit-Helium-Checkpoint-Created: true |
||
|
|
207d2ad52b |
Improve Arabic text display by fixing clipped dots on characters
Add a global CSS rule to increase line-height for truncated Arabic text, ensuring diacritics are fully visible. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 8f7b38c9-dbac-45e9-86d9-0cb4f4a5d011 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv Replit-Helium-Checkpoint-Created: true |