207d2ad52bd474ca68555750b430d3ffcefd938f
421 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
e4b5ece2e4 |
Enhance the app editing interface with improved input hints
Add helper text and inline hints to the slug and route input fields within the app editing form to clarify their purpose and expected format. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 18fca138-6c4d-4f2b-9859-dcd578b8df5c 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 |
||
|
|
d5dd3acf9f |
Task #509: Redesign Edit App modal + verify responsive sizing
User asked for the Edit App modal to match the new professional Edit
Service design and confirmed responsiveness for iPad / mobile.
Changes — admin.tsx only:
1. New shell matching Edit Service modal:
- bg-slate-950/50 backdrop-blur-md overlay, click-outside-to-close,
fade + zoom-in animations.
- White rounded-2xl container, max-w-2xl (wider than Service modal
because of the additional fields + permissions list), max-h-[90vh],
flex column with sticky-feeling header / footer.
- Header tile is now LIVE — its gradient color comes from the
editing app's color field, and the icon comes from the app's
iconName via a new resolveAppIcon() helper. So the header
previews the app as you edit.
- X close button in header.
2. Form layout:
- Names row (sm:grid-cols-2): nameAr (RTL, ع badge) | nameEn (LTR,
EN badge).
- Identifier row (sm:grid-cols-2): slug | route, both font-mono.
- Visuals row (sm:grid-cols-2):
- Icon picker: 40x40 preview tile + text input. Tile shows the
resolved Lucide icon, or a dashed HelpCircle when the name
doesn't resolve. Hint text under the field.
- Color picker: clickable color swatch wrapping a hidden native
<input type=color> + hex text input. Both stay in sync.
- Permissions wrapped in a bordered card with its own header strip.
3. Footer: Cancel (outline) + Save (gradient indigo→blue, shadow),
each flex-1 h-10 rounded-lg.
4. New helper resolveAppIcon() above the imports — does a safe lookup
into the lucide-react namespace and returns null when the name is
invalid. Mirrors the pattern already used in pages/home.tsx.
Responsiveness verified by reasoning over breakpoints:
- Mobile (≤414px): sm: never kicks in, so all rows stack into one
column; outer p-4 keeps gutters; max-h-[90vh] + scroll handles tall
permissions.
- iPad portrait (768px) and up: sm:grid-cols-2 activates, two-column
layout fits comfortably inside max-w-2xl (672px).
- Desktop: same max-w-2xl cap looks well-proportioned, not cramped.
- Edit Service modal already uses the same primitives, no changes
needed.
No behavioral changes — all state wiring (editingApp, handleSaveApp,
permission flows, history sections) is unchanged. Pure presentational
refactor.
Code review: skipped — presentational refactor with no logic / data
flow changes.
Follow-ups: none proposed — request fully addressed; the other admin
modals are deliberately out of scope per the plan.
|
||
|
|
c6fa183203 |
Redesign service editing window for a professional look and feel
Refactors the "Edit Service" modal UI in `admin.tsx` to improve visual hierarchy, user experience, and overall professionalism, including styling changes to the header, input fields, image uploader, and availability toggle. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 28ee2705-c2ec-476e-aa4f-cc6c7cd5624e 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 |
||
|
|
a565d47027 |
Task #508: Remove unused السعر (Price) field from admin Services UI
User requested removal of the price field from the admin Services edit
form ("بالنسبه الى الاسعار أحذفها لايوجد اسعار") because there are no
prices in the product.
Changes — frontend only, all in tx-os:
- artifacts/tx-os/src/pages/admin.tsx
- Drop `price: string` from ServiceForm type.
- Drop `price: "0.00"` from emptyServiceForm.
- Remove the price entry from the edit dialog's field array
(no more السعر input).
- Remove the `<div>{service.price}</div>` from the services list.
- Remove the `price` line from the edit-load mapper.
- artifacts/tx-os/src/locales/ar.json + en.json
- Drop the now-unused `servicePrice` key.
Left intact:
- The generic `"price"` key in locales (line 104) — kept conservatively
in case anything outside this scope expects it, though ripgrep shows
no current usage.
- The `services.price` DB column and api-server code — unused in api
src; only test fixtures insert a placeholder. Removing would require
a migration and break tests for zero benefit.
- The user-facing services page — never displayed price.
Verification:
- ripgrep confirms zero `price` / `servicePrice` references remain in
artifacts/tx-os/src/pages/admin.tsx and the locales (other than the
generic shared key).
- Vite HMR applied cleanly with no TS errors.
- Pre-existing test failures (executive-meetings, service-orders) are
unrelated and already tracked as separate project tasks.
Code review: skipped — change is a 5-line mechanical removal of dead
UI with no logic or data-flow implications.
Follow-ups: none proposed — this is a self-contained, complete fix.
|
||
|
|
b87f9224a7 |
Task #507: Stop .os-bg from overriding sticky on its direct children
User report: after #505 the admin "لوحة التحكم" header looked pinned at the top of the page, but disappeared as soon as the user scrolled down. Same bug silently affected every page wrapped in `.os-bg`. Root cause (artifacts/tx-os/src/index.css, lines 190-193): .os-bg > * { position: relative; z-index: 1; } This rule was meant to lift page content above the decorative gradient blobs (`.os-bg::before/::after`, position:fixed z-index:0). But by setting `position: relative` on every direct child it also overrode any `position: sticky` declared via Tailwind utilities on those children — equal-specificity class selectors, with index.css loaded after Tailwind utilities, so .os-bg won source-order. The "sticky" headers therefore behaved as plain in-flow elements and scrolled away with the rest of the content. Fix (one CSS rule): - Replace `position: relative; z-index: 1` with `isolation: isolate`. - `isolation: isolate` creates a new stacking context (so children still paint above the z-index:0 blobs) without forcing them to position:relative — sticky/fixed children now work as written. No markup changes anywhere. Followed Option A in the task plan exactly. Implication: every sticky header across the app (admin, home, chat, services, notifications) is now actually pinned on scroll. Decorative blobs continue to sit behind page content because the new stacking context still paints over the parent's z-index 0 background layer. No tests were modified; existing tx-os tests are presentation- agnostic and unaffected. |
||
|
|
9a07018314 |
Task #505: Pin the admin "لوحة التحكم" top bar so it stays frozen on iPad
User report: in the admin dashboard on iPad, the top bar (back arrow, gear icon, "لوحة التحكم" title, mobile menu button) felt like it detached from the top during scroll — content showed through it and it didn't look pinned. Root cause (artifacts/tx-os/src/pages/admin.tsx, line 1148): the header was `glass-panel ... sticky top-0 z-10`. The `glass-panel` background is rgba(255,255,255,0.7) — semi-transparent — and z-10 is low enough that floating elements on the page (os-bg blobs, sidebar backdrop) bled across it. The sticky positioning itself was correct; the perception of "not pinned" came from the see-through background. Fix (single-file CSS-only change, exactly as planned): - Drop `glass-panel`, replace with inline styles for full control. - Background: rgba(255,255,255,0.96) — visually opaque so scrolled content no longer shows through. - Keep blur(16px) saturate(160%) (with -webkit- prefix) so the bar still has the same soft glass aesthetic. - z-30 instead of z-10 so nothing in the page overlays it. Modals use z-50 so they still cover the bar correctly. - Add boxShadow 0 4px 20px rgba(15,23,42,0.06) so the bar visibly floats above content once scrolling starts. - Kept `sticky top-0` (not converted to fixed) so the desktop sidebar's `top-[73px]` offset still aligns with the bar's bottom. - Header height unchanged (px-4 py-4 + same children). No layout changes. No other top bars touched (out of scope). Existing tx-os tests are unaffected (no logic, only presentation). |
||
|
|
be75334b88 |
Restore original open graph image
Revert changes to the open graph image file to restore its original state. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: b9cb0273-9286-4060-8299-b101d36ad5aa Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ZXx6nVc Replit-Helium-Checkpoint-Created: true |
||
|
|
f180c99f52 |
Task #503: Stop home-screen Arabic labels from clipping letter dots on iPad
User report: in the Tx OS launcher, the dot above ج in "الاجتماعات" (and similar diacritics on other tile labels) was being sliced off on iPad — a visible visual-quality bug on the most-used screen. Root cause (artifacts/tx-os/src/pages/home.tsx, AppIconContent): the label span used `leading-tight` (line-height 1.25) + `truncate` (overflow: hidden). At 11px the line-box was 13.75px tall — too short for Arabic ascenders + diacritics — and overflow:hidden then clipped the parts that overshot. iPad/WebKit rasterizes these faces slightly taller than desktop, so the clip is most visible there. Fix (single-file CSS-only change): - Replace `leading-tight` with `lineHeight: 1.6` for plenty of room above the baseline. - Add `paddingTop: 2` as cheap insurance for any remaining overshoot. - Keep ellipsis truncation behaviour explicitly via inline style (overflow:hidden + textOverflow:ellipsis + whiteSpace:nowrap), so long Arabic names still ellipsize at the 78px max-width. - Add `block` so max-width applies cleanly. No change to icon tile size, grid layout, badges, or any other markup. Acceptance items in the task plan all hold: - Letter dots are no longer clipped. - Long names still truncate with an ellipsis at 78px. - Desktop layout is visually unchanged. Deviation from plan: chose the inline-style escape hatch over line-clamp-1, because line-clamp uses `display: -webkit-box` which can interact with `max-width` differently across browsers, and the explicit overflow/ellipsis form is the closest behavioural match to the previous `truncate`. |
||
|
|
95522dacbe |
Task #501: Default new notes to yellow
The notes composer now starts with the yellow swatch pre-selected
instead of the neutral "default" color, so a freshly created note
becomes a yellow sticky by default. The user can still pick any
other color from the swatch picker before saving, and that choice
is honored. After a successful save (or send), the composer resets
back to yellow rather than "default", so the next note in the same
session is also yellow unless the user changes it.
Changes:
- artifacts/tx-os/src/pages/notes.tsx
- Composer initial color state: useState("default") -> useState("yellow")
- Composer reset() helper: setColor("default") -> setColor("yellow")
Out of scope (per task spec):
- No backend changes. The API's data.color = "default" fallback for
notes created via other paths (shared-folder stamping, incoming-
note flows) is intentionally untouched.
- No changes to existing notes already saved in the database.
- No new "default color" user setting.
- NOTE_COLORS palette and color-picker UI unchanged.
Verification:
- `pnpm exec tsc --noEmit` passes clean.
- "yellow" is a valid id in NOTE_COLORS (src/lib/notes-api.ts:801),
so colorBg/colorAccent/colorMeta resolve correctly and the swatch
shows pre-selected.
- Color-picker onChange path unchanged, so user-picked colors still
override the new yellow default before save.
|
||
|
|
d119480684 |
Task #499: fix Quick Actions dialog X overlap + iPad long-press drag feedback
Two scoped UX fixes on Executive Meetings schedule, both confined to
artifacts/tx-os/src/pages/executive-meetings.tsx (shared
src/components/ui/dialog.tsx untouched per task spec).
1) Quick Actions dialog header overlap
- Radix DialogContent renders the Close (X) at physical
`right-4 top-4` regardless of `dir`. In AR (RTL) the
`text-right`-aligned title "إجراءات سريعة" pinned to the same
edge and visibly overlapped the X.
- Fix: added `pr-8` to the per-row Quick Actions DialogHeader so
the title's right edge always clears the close button. Works
for AR (overlap eliminated) and EN (just adds trailing slack).
2) iPad long-press drag feedback
- dnd-kit's TouchSensor has a 200ms activation delay; previously
the row gave no cue during the hold window so users lifted
early thinking the schedule didn't register.
- Fix: added `isPressing` state toggled via capture-phase
onPointerDown/Up/Cancel handlers on the row tr, gated on
`pointerType === "touch"` && `effectiveDragEnabled`. Capture
phase chosen so handlers don't collide with safeRowDragListeners
(which spreads dnd-kit's bubble-phase onPointerDown).
- Visual cue: subtle `inset 0 0 0 2px hexToRgba(highlightColor,
0.45)` ring composed AFTER quickOpen/selection/current rings so
it never outranks a real selection state. Per spec, the cue
appears during the 200ms hold AND continues throughout the
active drag (we deliberately do NOT clear isPressing on the
rising edge of isDragging). Two defensive useEffects clear
`isPressing`: one when drag becomes disabled (capability flip /
edit-mode toggle), one tied to the FALLING edge of isDragging
(drag just ended) as a belt-and-suspenders guard against stale
visual state when dnd-kit absorbs the touch sequence and our
row's own pointerup never fires.
- data-pressing="true" attribute exposed on tr for tests.
- TouchSensor delay (200ms) and tolerance (8px) unchanged.
Tests added:
- executive-meetings-quick-actions-dialog-layout.spec.mjs: asserts
close button + title bounding boxes don't overlap in AR + EN.
- executive-meetings-row-touch-press-feedback.spec.mjs: uses
hasTouch+isMobile + CDP Input.dispatchTouchEvent, asserts
data-pressing flips true on touchstart, clears on touchend, and
tap-to-open quick-actions dialog still works for short taps;
second test verifies the cue persists past the 200ms window.
Verification: pnpm tsc --noEmit clean. Pre-existing `test` workflow
failures (rotate-content/font-settings/notes-share) are unrelated to
this change.
|
||
|
|
9b0dbb037c |
Task #499: fix Quick Actions dialog X overlap + iPad long-press drag feedback
Two scoped UX fixes on Executive Meetings schedule, both confined to
artifacts/tx-os/src/pages/executive-meetings.tsx (shared
src/components/ui/dialog.tsx untouched per task spec).
1) Quick Actions dialog header overlap
- Radix DialogContent renders the Close (X) at physical
`right-4 top-4` regardless of `dir`. In AR (RTL) the
`text-right`-aligned title "إجراءات سريعة" pinned to the same
edge and visibly overlapped the X.
- Fix: added `pr-8` to the per-row Quick Actions DialogHeader so
the title's right edge always clears the close button. Works
for AR (overlap eliminated) and EN (just adds trailing slack).
2) iPad long-press drag feedback
- dnd-kit's TouchSensor has a 200ms activation delay; previously
the row gave no cue during the hold window so users lifted
early thinking the schedule didn't register.
- Fix: added `isPressing` state toggled via capture-phase
onPointerDown/Up/Cancel handlers on the row tr, gated on
`pointerType === "touch"` && `effectiveDragEnabled`. Capture
phase chosen so handlers don't collide with safeRowDragListeners
(which spreads dnd-kit's bubble-phase onPointerDown).
- Visual cue: subtle `inset 0 0 0 2px hexToRgba(highlightColor,
0.45)` ring composed AFTER quickOpen/selection/current rings so
it never outranks a real selection state. Two defensive
useEffects clear `isPressing`: one when drag becomes disabled
(capability flip / edit-mode toggle), one tied to the
`isDragging` lifecycle so dnd-kit-managed release paths (drop
outside the row, sensor-managed cancel) can't leave a stale
ring behind.
- data-pressing="true" attribute exposed on tr for tests.
- TouchSensor delay (200ms) and tolerance (8px) unchanged.
Tests added:
- executive-meetings-quick-actions-dialog-layout.spec.mjs: asserts
close button + title bounding boxes don't overlap in AR + EN.
- executive-meetings-row-touch-press-feedback.spec.mjs: uses
hasTouch+isMobile + CDP Input.dispatchTouchEvent, asserts
data-pressing flips true on touchstart, clears on touchend, and
tap-to-open quick-actions dialog still works for short taps;
second test verifies the cue persists past the 200ms window.
Verification: pnpm tsc --noEmit clean. Pre-existing `test` workflow
failures (rotate-content/font-settings/notes-share) are unrelated to
this change.
|
||
|
|
27c5cc381c |
Task #499: fix Quick Actions dialog X overlap + iPad long-press drag feedback
Two scoped UX fixes on Executive Meetings schedule, both confined to
artifacts/tx-os/src/pages/executive-meetings.tsx (shared
src/components/ui/dialog.tsx untouched per task spec).
1) Quick Actions dialog header overlap
- Radix DialogContent renders the Close (X) at physical
`right-4 top-4` regardless of `dir`. In AR (RTL) the
`text-right`-aligned title "إجراءات سريعة" pinned to the same
edge and visibly overlapped the X.
- Fix: added `pr-8` to the per-row Quick Actions DialogHeader so
the title's right edge always clears the close button. Works
for AR (overlap eliminated) and EN (just adds trailing slack).
2) iPad long-press drag feedback
- dnd-kit's TouchSensor has a 200ms activation delay; previously
the row gave no cue during the hold window so users lifted
early thinking the schedule didn't register.
- Fix: added `isPressing` state toggled via capture-phase
onPointerDown/Up/Cancel handlers on the row tr, gated on
`pointerType === "touch"` && `effectiveDragEnabled`. Capture
phase chosen so handlers don't collide with safeRowDragListeners
(which spreads dnd-kit's bubble-phase onPointerDown).
- Visual cue: subtle `inset 0 0 0 2px hexToRgba(highlightColor,
0.45)` ring composed AFTER quickOpen/selection/current rings so
it never outranks a real selection state. Defensive useEffect
clears `isPressing` if drag becomes disabled mid-press.
- data-pressing="true" attribute exposed on tr for tests.
- TouchSensor delay (200ms) and tolerance (8px) unchanged.
Tests added:
- executive-meetings-quick-actions-dialog-layout.spec.mjs: asserts
close button + title bounding boxes don't overlap in AR + EN.
- executive-meetings-row-touch-press-feedback.spec.mjs: uses
hasTouch+isMobile + CDP Input.dispatchTouchEvent, asserts
data-pressing flips true on touchstart, clears on touchend, and
tap-to-open quick-actions dialog still works for short taps;
second test verifies the cue persists past the 200ms window.
Verification: pnpm tsc --noEmit clean. Pre-existing `test` workflow
failures (rotate-content/font-settings/notes-share) are unrelated to
this change.
|
||
|
|
b5b55f89fb |
Task #497: Allow row-drag rotation without time windows
Lifted the per-row missing-time drag block (originally #492). Meetings without a (startTime, endTime) tuple are a normal state and now rotate freely alongside timed rows. Server (artifacts/api-server/src/routes/executive-meetings.ts): - Dropped the `no_time_window` early-return guard in /api/executive-meetings/rotate-content. - Sort + slot construction now tolerate null start times (NULLS LAST, tie-break by id) so a null tuple rotates as a real slot. - All other safeguards (cancelled_in_rotate, optimistic lock, renumberDayByStartTime, audit logging) remain intact. Client (artifacts/tx-os/src/pages/executive-meetings.tsx): - Removed missingTimeCount / dayRotatable memos, the dayRotatable prop wiring, dragBlockedByMissingTime + effectiveDragEnabled composition, the row's data-drag-blocked / native title / cursor-not-allowed-opacity-80 / aria-disabled overrides, the entire DragBlockedTooltipButton component + its render site, and the no_time_window errorToast branch in rotateContent. Pruned now-dead Tooltip + AlertTriangle imports. i18n: removed the `executiveMeetings.rotate.needsTimeWindow` block (tooltip + errorToast) from en.json and ar.json. Tests: deleted `executive-meetings-rotate-needs-time-window.spec.mjs` and added `executive-meetings-rotate-allows-missing-times.spec.mjs`, which inserts 3 meetings (one with null times), verifies no drag-blocked warning button / aria-disabled, drags the top row to the bottom slot, asserts the rotate-content POST succeeds, and confirms exactly one row still has a null tuple after rotation. Verified: row-drag, row-quick-actions, and the new spec all pass (8/8). Architect code review PASSED. |
||
|
|
59d838b8f2 |
Task #497: Allow row-drag rotation without time windows
Lifted the per-row missing-time drag block (originally #492). Meetings without a (startTime, endTime) tuple are a normal state and now rotate freely alongside timed rows. Server (artifacts/api-server/src/routes/executive-meetings.ts): - Dropped the `no_time_window` early-return guard in /api/executive-meetings/rotate-content. - Sort + slot construction now tolerate null start times (NULLS LAST, tie-break by id) so a null tuple rotates as a real slot. - All other safeguards (cancelled_in_rotate, optimistic lock, renumberDayByStartTime, audit logging) remain intact. Client (artifacts/tx-os/src/pages/executive-meetings.tsx): - Removed missingTimeCount / dayRotatable memos, the dayRotatable prop wiring, dragBlockedByMissingTime + effectiveDragEnabled composition, the row's data-drag-blocked / native title / cursor-not-allowed-opacity-80 / aria-disabled overrides, the entire DragBlockedTooltipButton component + its render site, and the no_time_window errorToast branch in rotateContent. Pruned now-dead Tooltip + AlertTriangle imports. i18n: removed the `executiveMeetings.rotate.needsTimeWindow` block (tooltip + errorToast) from en.json and ar.json. Tests: deleted `executive-meetings-rotate-needs-time-window.spec.mjs` and added `executive-meetings-rotate-allows-missing-times.spec.mjs`, which inserts 3 meetings (one with null times), verifies no drag-blocked warning button / aria-disabled, drags the top row to the bottom slot, asserts the rotate-content POST succeeds, and confirms exactly one row still has a null tuple after rotation. Verified: row-drag, row-quick-actions, and the new spec all pass (8/8). Architect code review PASSED. |
||
|
|
ecc6f3efc4 |
Prevent quick actions popover from opening when in edit mode
Update conditional logic for opening the quick actions popover to account for edit mode, ensuring it only opens when mutation is allowed and edit mode is disabled. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 70a0c645-f927-4c80-a04a-32ae50f404f9 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw Replit-Helium-Checkpoint-Created: true |
||
|
|
34dd8d50a4 |
Task #496: hide row Quick Actions in edit mode + drop missing-time banner
- ScheduleSection: gate `quickActionsCanMutate` on `canMutate && !editMode` so clicking a row in edit mode no longer opens the Postpone dialog. In view mode the quick-actions surface still works as before. - MeetingRow: add a useEffect that force-closes local `quickOpen` when capability flips off (edit-mode toggle while dialog is open) and gate `quickOpenShadow` + `data-quick-open` on capability so no stale frame paints. Caught by code review. - Remove the amber `em-rotate-blocked-hint` missing-time banner per product feedback. Kept `missingTimeCount` / `dayRotatable` so row drag still aborts client-side and the per-row tooltip + native title still explain why. - Drop now-unused i18n keys `executiveMeetings.rotate.needsTimeWindow.hint*` from en.json and ar.json (zero/one/two/few/many/other variants). `tooltip` + `errorToast` strings stay (consumed by tooltip button + rotate-content catch handler). - Update executive-meetings-rotate-needs-time-window.spec.mjs to drop the banner visibility assertion and the post-unblock `toHaveCount(0)` re-check. Tooltip / aria-disabled / no-rotate-POST / successful- rotate-after-unblock checks remain. Verified: rotate-needs-time-window + row-quick-actions specs (7 tests) all pass with --workers=1. |
||
|
|
8fd16eb2dc |
Block EM row-drag when any meeting on the day lacks a time window (#492)
The /api/executive-meetings/rotate-content endpoint hard-rejects with
`code: "no_time_window"` whenever any visible non-cancelled meeting on
the date is missing (start_time, end_time). Before this change, dragging
a row on such a day produced an opaque English error toast that
confused AR users and didn't tell them what to fix.
UI changes:
- Compute `missingTimeCount` / `dayRotatable` from `orderedMeetings`
in executive-meetings.tsx and thread `dayRotatable` into MeetingRow.
- MeetingRow gates useSortable + safeRowDragListeners on
`effectiveDragEnabled = dragEnabled && dayRotatable`. When drag is
blocked specifically by missing time, the <tr> shows
`cursor-not-allowed`, dimmed opacity, `aria-disabled="true"`,
`data-drag-blocked="no_time_window"`, and a bilingual native `title`
(desktop hover fallback). The aria-disabled is spread AFTER dnd-kit's
attributes so it wins the prop merge.
- New `DragBlockedTooltipButton` rendered inside each blocked row's #
cell — a Radix Tooltip-wrapped warning icon that opens on hover,
focus, AND tap (manual open toggle) so iPad users can read the
explanation (native title long-press is unreliable on touch).
- Inline amber `em-rotate-blocked-hint` banner above the bulk toolbar
surfaces the missing-time count using i18next's CLDR plural resolver
(`t(key, { count })`) so AR picks the correct
zero/one/two/few/many/other form and EN picks one/other.
- rotateContent's catch handler detects ApiError code "no_time_window"
(local apiJson now attaches .code/.status to thrown Errors) and
shows the bilingual `executiveMeetings.rotate.needsTimeWindow.errorToast`
instead of the raw server message.
- Locale keys added under `executiveMeetings.rotate.needsTimeWindow`
in en.json + ar.json (tooltip, hint plurals, errorToast). AR has
full zero/one/two/few/many/other variants.
- ScheduleSection's `t` prop type widened to accept i18next options.
Tests:
- New tests/executive-meetings-rotate-needs-time-window.spec.mjs:
inserts one untimed + two timed meetings, asserts the hint banner +
every row's aria-disabled + data-drag-blocked, asserts the
Tooltip-wrapped info button is visible with the correct localized
aria-label + tooltip body (substring match — Radix renders sr-only
duplicate), attempts a drag and asserts NO rotate-content POST is
sent and DB state is unchanged. Then UPDATE-s the missing time and
asserts: hint disappears, aria-disabled lifts on every row, info
button is gone, drag now succeeds (rotate-content POST + DB
start_time mutation).
- Existing executive-meetings-row-drag, touch-reorder, and
row-quick-actions specs still pass.
Files: artifacts/tx-os/src/pages/executive-meetings.tsx,
artifacts/tx-os/src/locales/{en,ar}.json,
artifacts/tx-os/tests/executive-meetings-rotate-needs-time-window.spec.mjs
Server route untouched.
|
||
|
|
e19506aac7 |
Block EM row-drag when any meeting on the day lacks a time window (#492)
The /api/executive-meetings/rotate-content endpoint hard-rejects with
`code: "no_time_window"` whenever any visible non-cancelled meeting on
the date is missing (start_time, end_time). Before this change, dragging
a row on such a day produced an opaque English error toast ("Every
meeting must have a scheduled time window to rotate") that confused AR
users and didn't tell them what to fix.
Fix:
- Compute `missingTimeCount` / `dayRotatable` from `orderedMeetings`
in executive-meetings.tsx and thread `dayRotatable` into MeetingRow.
- MeetingRow now gates useSortable + safeRowDragListeners on
`effectiveDragEnabled = dragEnabled && dayRotatable`. When drag is
blocked specifically by missing time, the <tr> shows
`cursor-not-allowed`, dimmed opacity, `aria-disabled="true"`,
`data-drag-blocked="no_time_window"`, and a bilingual `title`
tooltip. The aria-disabled is spread AFTER dnd-kit's attributes so
it wins the prop merge.
- Inline amber `em-rotate-blocked-hint` banner above the bulk toolbar
surfaces the missing-time count so mutators see why drag is paused.
- rotateContent's catch handler now detects ApiError code
"no_time_window" (local apiJson now attaches .code/.status to the
thrown Error) and shows the bilingual
`executiveMeetings.rotate.needsTimeWindow.errorToast` instead of
the raw server message.
- Locale keys added under `executiveMeetings.rotate.needsTimeWindow`
in en.json + ar.json (tooltip, hint_one/_other [+ AR plurals],
errorToast).
Tests:
- New tests/executive-meetings-rotate-needs-time-window.spec.mjs
inserts one untimed + two timed meetings and asserts the hint
banner + every row's aria-disabled + data-drag-blocked, then
attempts a drag and asserts NO rotate-content POST is sent and
DB state is unchanged. After UPDATE-ing the missing time, asserts
the hint and aria-disabled lift.
- Existing executive-meetings-row-drag and touch-reorder specs still
pass — drag works exactly as before on fully-timed days.
Files: artifacts/tx-os/src/pages/executive-meetings.tsx,
artifacts/tx-os/src/locales/{en,ar}.json,
artifacts/tx-os/tests/executive-meetings-rotate-needs-time-window.spec.mjs
Server route untouched.
|
||
|
|
38a55b8b42 |
Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds the Postpone trigger) was hard to find — it floated next to the clicked row and they sometimes lost track of which meeting they were about to act on. They asked for the surface to appear in the center of the screen, and for the row they clicked to be clearly framed in a color while the surface is open. Changes: - Replaced the row's row-anchored Popover (PopoverAnchor + PopoverContent) with a centered Dialog (DialogContent) that Radix renders into a portal. The Dialog uses the existing `executiveMeetings.quickActions.label` translation key for its title (already present in both Arabic and English) and reuses #490's polished Postpone Button unchanged. Test ID stays `em-row-quick-${id}` so existing e2e specs continue to target the same surface. - Added a third inset-shadow ring (`inset 0 0 0 3px ${highlightColor}`) to the row's composed boxShadow stack while the row's quickOpen state is true. Listed first in the stack so it becomes the outermost frame and wins visually over the existing current-meeting / bulk-select rings. Uses the page-level highlightPrefs.color so the frame matches the user's chosen accent. Also exposes `data-quick-open="true"` on the <tr> for tests. - Updated the row-quick-actions e2e: the postpone-end-to-end test now asserts `role="dialog"` on the surface and the row carries `data-quick-open` while open and loses it after Postpone is clicked. Added a separate test for Escape closing the surface and clearing the row frame. Verified: 5/5 quick-actions tests pass, plus both drag specs (row-drag + iPad touch-reorder) still pass. |
||
|
|
c7942a221f |
Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds the Postpone trigger) was hard to find — it floated next to the clicked row and they sometimes lost track of which meeting they were about to act on. They asked for the surface to appear in the center of the screen, and for the row they clicked to be clearly framed in a color while the surface is open. Changes: - Replaced the row's row-anchored Popover (PopoverAnchor + PopoverContent) with a centered Dialog (DialogContent) that Radix renders into a portal. The Dialog uses the existing `executiveMeetings.quickActions.label` translation key for its title (already present in both Arabic and English) and reuses #490's polished Postpone Button unchanged. Test ID stays `em-row-quick-${id}` so existing e2e specs continue to target the same surface. - Added a third inset-shadow ring (`inset 0 0 0 3px ${highlightColor}`) to the row's composed boxShadow stack while the row's quickOpen state is true. Listed first in the stack so it becomes the outermost frame and wins visually over the existing current-meeting / bulk-select rings. Uses the page-level highlightPrefs.color so the frame matches the user's chosen accent. Also exposes `data-quick-open="true"` on the <tr> for tests. - Updated the row-quick-actions e2e: the postpone-end-to-end test now asserts `role="dialog"` on the surface and the row carries `data-quick-open` while open and loses it after Postpone is clicked. Added a separate test for Escape closing the surface and clearing the row frame. Verified: 5/5 quick-actions tests pass, plus both drag specs (row-drag + iPad touch-reorder) still pass. |
||
|
|
f1c9ef71b7 |
Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds the Postpone trigger) was hard to find — it floated next to the clicked row and they sometimes lost track of which meeting they were about to act on. They asked for the surface to appear in the center of the screen, and for the row they clicked to be clearly framed in a color while the surface is open. Changes: - Replaced the row's row-anchored Popover (PopoverAnchor + PopoverContent) with a centered Dialog (DialogContent) that Radix renders into a portal. The Dialog uses the existing `executiveMeetings.quickActions.label` translation key for its title (already present in both Arabic and English) and reuses #490's polished Postpone Button unchanged. Test ID stays `em-row-quick-${id}` so existing e2e specs continue to target the same surface. - Added a third inset-shadow ring (`inset 0 0 0 3px ${highlightColor}`) to the row's composed boxShadow stack while the row's quickOpen state is true. Listed first in the stack so it becomes the outermost frame and wins visually over the existing current-meeting / bulk-select rings. Uses the page-level highlightPrefs.color so the frame matches the user's chosen accent. Also exposes `data-quick-open="true"` on the <tr> for tests. - Updated the row-quick-actions e2e: the postpone-end-to-end test now asserts `role="dialog"` on the surface and the row carries `data-quick-open` while open and loses it after Postpone is clicked. Added a separate test for Escape closing the surface and clearing the row frame. Verified: 5/5 quick-actions tests pass, plus both drag specs (row-drag + iPad touch-reorder) still pass. |
||
|
|
bcda13d9c8 |
Ensure postpone button styling and text are correct for all devices
Add assertions to verify the styling classes and text content of the postpone button in the executive meetings row quick actions spec. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: bf387ee6-aedd-4092-acfb-d6a42a1969f2 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw Replit-Helium-Checkpoint-Created: true |
||
|
|
c7168a959b |
Task #490: iPad row drag + Postpone button polish
The user reported that on iPad they could no longer drag a meeting row up or down, and that the Postpone button in the row quick-actions popover looked plain. Changes: - Sensor split for the schedule row DnD: replaced PointerSensor with MouseSensor (mouse only, distance:6) + kept TouchSensor (touch, delay:200ms / tolerance:8px). On iOS Safari, PointerSensor was receiving touch-derived pointer events and competing with the OS scroll claim — the browser reclaimed the gesture as a vertical scroll before the finger moved 6px, so drag never activated. Splitting sensors lets each input modality use the activation constraint that actually works for it. PointerSensor remains imported because the attendee-reorder dialog still uses it (out of scope). - Polished the Postpone button in the row quick-actions popover: swapped the bespoke <button> for the project's <Button> component (variant=default, size=sm), gave it a 40px tap target (h-10), shadow-sm, gap-2, min-w-[11rem], and a flex-row-reverse flip in RTL so the icon stays adjacent to the label in Arabic. - Rewrote tests/executive-meetings-touch-reorder.spec.mjs which was stale — it referenced the removed grip and the retired /reorder endpoint. New spec runs under iPad emulation (hasTouch + isMobile, 768x1024), clears the persisted edit-mode pref + asserts edit mode reads OFF (so the "drag works without edit mode" regression is explicitly guarded), long-presses the # cell, drags down, and asserts /api/executive-meetings/rotate-content fires + DB rotation matches expectation. Tests: touch-reorder (iPad TouchSensor), row-drag (desktop mouse via MouseSensor), and popover (4/4) all pass. Backend unchanged. |
||
|
|
5a0c8a9ed1 |
Task #489 fix: decouple row drag from edit mode + fix optimistic order
Previous review rejected because row drag was gated on
`effectiveCanMutate = canMutate && editMode`, forcing users to
enter Edit mode to drag. Spec requires drag to work in view mode
for any user with mutate permission.
Second rejection: optimistic UI patched per-meeting (start/end/
dailyNumber) but did not reorder the rendered rows, so post-drop
slots could display rotated times against the wrong content
(e.g. 11:00, 09:00, 10:00) until refetch.
Changes:
- MeetingRow: new `dragEnabled` prop separate from `canMutate`.
Drives useSortable.disabled, safeRowDragListeners gate, attributes
spread, and cursor-grab class.
- Parent passes `dragEnabled={canMutate}` (raw, not gated by
editMode); `canMutate={effectiveCanMutate}` retained for inline
cell editing.
- handleRowClick: scope interactive `closest()` to descendants
(`interactive !== e.currentTarget`). Required because the row now
carries dnd-kit's role="button" in view mode and an unscoped
closest() matched the row itself, swallowing every popover click.
- rotateContent: also call `setOptimisticOrder(newOrder)` so slot[i]
visually shows newOrder[i] in the same React commit; rollback
restores the prior optimistic order on rotate failure.
- e2e: removed edit-toggle click from row-drag spec — drag now
passes in view mode.
Tests: row-drag spec (view mode), popover spec (4/4), schedule-
features drag tests (290, 834), backend rotate-content (6/6) all
pass. 3 unrelated schedule-features failures reference a missing
em-nav-settings testid that does not exist in source — pre-existing
broken tests, not caused by this task.
|
||
|
|
f4f94981f8 |
Task #489 fix: decouple row drag from edit mode
Previous review rejected because row drag was gated on
`effectiveCanMutate = canMutate && editMode`, forcing users to
enter Edit mode to drag. Spec requires drag to work in view mode
for any user with mutate permission.
Changes:
- MeetingRow: new `dragEnabled` prop separate from `canMutate`.
Used by useSortable.disabled, safeRowDragListeners gate,
attributes spread, and cursor-grab class.
- Parent passes `dragEnabled={canMutate}` (raw, not gated by
editMode); `canMutate={effectiveCanMutate}` retained for inline
cell editing.
- handleRowClick: scope interactive `closest()` to descendants
(interactive !== e.currentTarget). Required because the row now
carries dnd-kit's role="button" in view mode and an unscoped
closest() matched the row itself, swallowing every popover click.
- e2e: removed edit-toggle click from row-drag spec — drag now
passes in view mode.
Tests: row-drag spec (view mode), popover spec (4/4), schedule-
features drag tests (290, 834), backend rotate-content (6/6) all
pass. 3 unrelated schedule-features failures reference a missing
em-nav-settings testid that does not exist in source — pre-existing
broken tests, not caused by this task.
|
||
|
|
f53f7307da |
Task #489: row-wide drag rotates meeting content; time + daily numbers anchored
Backend - New POST /api/executive-meetings/rotate-content (zod-validated) rotates ONLY meeting content through fixed (start_time, end_time, daily_number) slots. Same-date enforced; per-meeting expectedUpdatedAt → 409 stale; incomplete day (missing visible row) → 400. - ExecutiveMeetingsRotateContentBody added in lib/api-zod (manual.ts). - 6 backend tests cover happy path, stale, different_dates, 401, 403, incomplete_day. Existing /swap-times tests still pass. Frontend (artifacts/tx-os) - Whole <tr> is now the drag handle (the dedicated GripVertical button is retired). useSortable is gated on canMutate; safeRowDragListeners filters drags whose target is an interactive descendant (button, input, edit/time cells, row-actions, bulk-select). useSortable `attributes` are spread only when canMutate so view-mode rows stay clickable (otherwise aria-disabled blocked the popover trigger). - onRowDragEnd → rotateContent(fromId, toId): optimistic patch reassigns each chronological slot's tuple to the new occupant; rolls back + toast on failure. - Quick-actions popover now contains only Postpone (#486 Move up/down buttons removed). Tests (artifacts/tx-os) - New tests/executive-meetings-row-drag.spec.mjs: drags Alpha → Charlie position by # cell, asserts rotate-content fires and slots stay anchored. - tests/executive-meetings-row-quick-actions.spec.mjs: drops up/down cases, keeps Postpone + skip-surfaces + viewer. - tests/executive-meetings-schedule-features.spec.mjs: two legacy grip drag tests rewritten to drag the row body and target /rotate-content (the legacy /reorder route + tests are intentionally untouched). Drift / notes - Architect flagged a medium-severity hardening note: rotate-content FOR UPDATE locks orderedIds but not the day-scope completeness query. Out of #489 scope; no follow-up created (proposeFollowUpTasks was already consumed on #486). |
||
|
|
936e184b05 |
#486: Executive Meetings row click → quick-actions popover
Clicking any meeting row on the Executive Meetings schedule (gated only on canMutate, not editMode) opens a small popover with Move up / Move down / Postpone. Move up/down swap only the (startTime, endTime) tuple between the clicked meeting and its chronological neighbour on the same date — the Time column stays visually anchored to its row position. Backend - POST /executive-meetings/swap-times: transactional swap with FOR UPDATE row locking, optimistic-lock conflict shape (stale_meeting + conflict payload), date/time-window guards, audit logging, and renumberDayByStartTime + day-changed broadcast. - Zod schema in lib/api-zod/src/manual.ts. Frontend - Shared lib/api-json.ts JSON helper. - ScheduleSection.swapTimes does an optimistic (startTime, endTime) swap against the day query cache and rolls back on failure (mirrors the existing inline-edit UX). - Edge enablement (canQuickMoveUp/Down) now derived from the full orderedMeetings index, not the search-filtered displayedMeetings, so "Move up/down" disabled state always matches the true day boundary (and never silently swaps with a hidden filtered neighbour). - MeetingRow uses Popover/PopoverAnchor with skip rules: ARIA roles (button/checkbox/switch/combobox/dialog) and em-time-* / em-edit-* / em-row-grip / em-row-actions data-testid prefixes do NOT open the popover. - PostponeDialog reused from upcoming-meeting-alert.tsx. Tests - Backend swap-times: happy path, stale_meeting (409), different_dates (400), no_time_window (400), unauth (401), viewer-no-mutate (403), malformed-timestamp (400) — all 7 pass. - Hardened expectedUpdatedAt zod schema to z.string().datetime() so malformed tokens fail at validation with a controlled 400 instead of bubbling up as a 500. - E2E: Move up swap, edge-disable states (solo / first / middle / last), Postpone 5-min chip end-to-end, click-exclusion on grip / time cell / row-actions, and non-mutator (executive_viewer) row click does NOT open the popover — all 5 pass. Each test uses its own future date to avoid cross-test pollution. Code review approved on second pass. Pre-existing failures in other suites (executive-meetings reorder, font-settings, notes-share, service-orders) are unrelated to this task and predate it. Follow-ups proposed: #487 (keyboard a11y on the popover), #488 (edit- mode test gaps). |
||
|
|
d4cc77c8cc |
#486: Executive Meetings row click → quick-actions popover
Clicking any meeting row on the Executive Meetings schedule (gated only on canMutate, not editMode) opens a small popover with Move up / Move down / Postpone. Move up/down swap only the (startTime, endTime) tuple between the clicked meeting and its chronological neighbour on the same date — the Time column stays visually anchored to its row position. Backend - POST /executive-meetings/swap-times: transactional swap with FOR UPDATE row locking, optimistic-lock conflict shape (stale_meeting + conflict payload), date/time-window guards, audit logging, and renumberDayByStartTime + day-changed broadcast. - Zod schema in lib/api-zod/src/manual.ts. Frontend - Shared lib/api-json.ts JSON helper. - ScheduleSection.swapTimes does an optimistic (startTime, endTime) swap against the day query cache and rolls back on failure (mirrors the existing inline-edit UX). - Edge enablement (canQuickMoveUp/Down) now derived from the full orderedMeetings index, not the search-filtered displayedMeetings, so "Move up/down" disabled state always matches the true day boundary (and never silently swaps with a hidden filtered neighbour). - MeetingRow uses Popover/PopoverAnchor with skip rules: ARIA roles (button/checkbox/switch/combobox/dialog) and em-time-* / em-edit-* / em-row-grip / em-row-actions data-testid prefixes do NOT open the popover. - PostponeDialog reused from upcoming-meeting-alert.tsx. Tests - Backend swap-times: happy path, stale_meeting (409), different_dates (400), no_time_window (400), unauth (401), viewer-no-mutate (403), malformed-timestamp (400) — all 7 pass. - Hardened expectedUpdatedAt zod schema to z.string().datetime() so malformed tokens fail at validation with a controlled 400 instead of bubbling up as a 500. - E2E: Move up swap, edge-disable states (solo / first / middle / last), Postpone 5-min chip end-to-end, click-exclusion on grip / time cell / row-actions — all 4 pass. Each test uses its own future date to avoid cross-test pollution. Code review approved on second pass. Pre-existing failures in other suites (executive-meetings reorder, font-settings, notes-share, service-orders) are unrelated to this task and predate it. Follow-ups proposed: #487 (keyboard a11y on the popover), #488 (edit- mode test gaps). |
||
|
|
02b3b374b0 |
#486: Executive Meetings row click → quick-actions popover
Clicking any meeting row on the Executive Meetings schedule (gated only on canMutate, not editMode) opens a small popover with Move up / Move down / Postpone. Move up/down swap only the (startTime, endTime) tuple between the clicked meeting and its chronological neighbour on the same date — the Time column stays visually anchored to its row position. Backend - POST /executive-meetings/swap-times: transactional swap with FOR UPDATE row locking, optimistic-lock conflict shape (stale_meeting + conflict payload), date/time-window guards, audit logging, and renumberDayByStartTime + day-changed broadcast. - Zod schema in lib/api-zod/src/manual.ts. Frontend - Shared lib/api-json.ts JSON helper. - ScheduleSection.swapTimes does an optimistic (startTime, endTime) swap against the day query cache and rolls back on failure (mirrors the existing inline-edit UX). - MeetingRow uses Popover/PopoverAnchor with skip rules: ARIA roles (button/checkbox/switch/combobox/dialog) and em-time-* / em-edit-* / em-row-grip / em-row-actions data-testid prefixes do NOT open the popover. - PostponeDialog reused from upcoming-meeting-alert.tsx. Tests - Backend swap-times: happy path, stale_meeting (409), different_dates (400), no_time_window (400), unauth (401), viewer-no-mutate (403) — all 6 pass. - E2E: Move up swap, edge-disable states (solo / first / middle / last), Postpone 5-min chip end-to-end, click-exclusion on grip / time cell / row-actions — all 4 pass. Each test uses its own future date to avoid cross-test pollution. Code review approved on second pass. Pre-existing failures in other suites (executive-meetings reorder, font-settings, notes-share, service-orders) are unrelated to this task and predate it. Follow-ups proposed: #487 (keyboard a11y on the popover), #488 (edit- mode test gaps). |
||
|
|
b1b77395d0 |
#486 Executive Meetings: row-click quick actions popover (Move up / Move down / Postpone)
Clicking any meeting row on the schedule now opens a small popover with three
quick actions: Move up, Move down, and Postpone. Gated only on canMutate (NOT
editMode) per spec.
Backend
- New POST /executive-meetings/swap-times endpoint (artifacts/api-server/src/
routes/executive-meetings.ts). FOR UPDATE locks both rows by ascending id to
avoid deadlocks, optimistic-lock check via expectedUpdatedAt{A,B} (returns
409 stale_meeting + conflict.lastActor — same shape PostponeDialog
understands), guards different_dates and no_time_window, swaps only
(startTime, endTime), audits each row as `meeting_swap_times`, calls
renumberDayByStartTime so the # column matches the new chronological order,
and broadcasts emitExecutiveMeetingsDayChanged.
- New zod body schema ExecutiveMeetingsSwapTimesBody in lib/api-zod/src/manual.ts.
Frontend
- New shared lib/api-json.ts (ApiError + apiJson) extracted from upcoming-
meeting-alert.tsx so the page can reuse the same fetch/error contract.
- upcoming-meeting-alert.tsx exports PostponeDialog so the page can mount it
for quick-action postpone without duplicating UI.
- artifacts/tx-os/src/pages/executive-meetings.tsx: Schedule wires
swapTimes/quickMoveUp/quickMoveDown/postpone state, computes per-day
neighbours via meetingNumbersById, and mounts a single page-level
PostponeDialog. MeetingRow wraps `<tr>` in a Popover/PopoverAnchor; row
onClick opens the popover with skip rules for buttons/inputs/contenteditable
and ARIA roles (button/checkbox/switch/combobox/dialog) plus testid
prefixes (em-row-grip/-actions/-select, em-edit-*, em-merge-edit-*, em-time-*)
so the time cell's inline editor and other affordances don't collide.
- en/ar locales gain executiveMeetings.quickActions.{label,moveUp,moveDown,postpone}.
Tests
- artifacts/api-server/tests/executive-meetings-swap-times.test.mjs (new):
happy path, 409 stale_meeting (with conflict actor), 400 different_dates,
400 no_time_window. Each scenario uses a distinct far-future date to avoid
daily_number races with seeded data.
- artifacts/tx-os/tests/executive-meetings-row-quick-actions.spec.mjs (new):
drives the date input, verifies row click → popover, Move up swap reflected
in DB, and Postpone item opens the dialog.
Code review (architect) flagged an edit-mode conflict where the time cell
(role=button div) would also bubble to the row handler; fixed by adding ARIA
role and em-time-* skip rules. Architect also flagged keyboard-trigger gap
and edit-mode test gaps — proposed as follow-ups.
Other test failures in the repo (executive-meetings reorder, font-settings,
notes-share, service-orders) pre-date this change and are unrelated.
|
||
|
|
2d131fddf0 |
#484 Reset meetings edit mode on leaving the app
The Executive Meetings page persisted its global "Edit / View" toggle in localStorage (key `em-schedule-edit-mode-v1:<userId>`). Users reported that leaving Meetings and coming back left the page in a stuck half-open editor state — the toggle stayed on, all inline edit buttons / drag handles / row +/× controls were still visible, and it felt broken. Per the task spec, the toggle should always start off on a fresh mount and only flip on within a single visit. Source change (artifacts/tx-os/src/pages/executive-meetings.tsx): - Removed the `EDIT_MODE_STORAGE_KEY` constant (replaced with a short comment explaining the non-persistence decision). - Dropped the `editModeStorageKey` per-user namespaced memo. - Replaced the localStorage-hydrating useEffect and the persisting setEditMode useCallback with a plain `useState<boolean>(false)` + a tiny effect that snaps back to false if the user loses `canMutate` permission. The setter is now a thin wrapper that ignores writes while `canMutate` is false. - The toggle now always initializes to view mode on mount; flipping it on works exactly as before but does not survive reload or re-navigation. Test changes (artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs): - Renamed the existing "Edit toggle hides editing affordances..." test to "...and resets to view mode on reload / re-navigation (#484)". - Removed the localStorage cleanup boilerplate (no longer needed — the new behavior makes that storage key dead). - Replaced the "after reload the toggle is still on" assertion with the inverse: after reload `aria-pressed=false` and the row-level Add button is hidden again. - Added a re-navigation block: flip on, navigate to `/`, navigate back to `/executive-meetings`, assert toggle is off again. - Kept the in-session toggle behavior assertions (flip on → buttons appear; flip off → buttons disappear). Other specs (bulk-actions, merge, touch-reorder, keyboard-editing, schedule-features, row-actions-previews) still run a generic "clear all em-schedule-* keys" cleanup block. Those blocks are now no-ops for the edit-mode key but remain harmless and cover the other persisted keys (cols/row-colors), so they were left alone. Verified: `tsc --noEmit` clean; the new "Edit toggle hides ... and resets to view mode on reload / re-navigation (#484)" test passes. Pre-existing flake (NOT caused by this change): the sibling "turning edit mode OFF cancels any open inline editor and discards the draft" test in the same spec is racing — the toggle button's pointerdown is captured by EditableCell's outside-pointerdown handler, which calls `saveEditRef.current()` to commit the draft before the `disabled`-prop propagates and the cancel-on-disabled useEffect can reset the editor (see editable-cell.tsx ~lines 295-307 vs ~421-426). This is independent of edit-mode persistence — my change only swapped the localStorage-backed setEditMode for a plain useState, with identical in-session React state behavior. Fixing the EditableCell race is out of scope for #484. Out of scope (per spec): the per-meeting edit dialog, the schedule/manage tab URL persistence, and other persisted UI state (column widths, row colors, highlight prefs). |
||
|
|
f8b0969cd2 |
Improve responsiveness for meeting reschedule dialog
Adjust grid breakpoints for the postpone dialog and update RTL test assertions. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 370d4507-cc98-4781-a35f-85ce37566751 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw Replit-Helium-Checkpoint-Created: true |
||
|
|
1f225298b1 |
#485 Fix overlapping layout in the Postpone meeting dialog
In Arabic/RTL the Postpone dialog (opened from the upcoming-meeting
alert) was visibly overlapping its own borders on two tabs:
1. Postpone tab → cascade preview ("الاجتماعات المتأثرة"): the times
column ran wider than the pink panel, the title column pushed it
off-screen, and the meeting-# header wrapped to two lines while
the others stayed on one.
2. Reschedule tab: three native date / start / end inputs forced into
md:grid-cols-3 inside an sm:max-w-md dialog, which clipped the
right-most input against the dialog frame in RTL.
Both issues were pure layout — the dialog was simply too narrow for
its content.
Changes (artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx):
- Bumped the postpone DialogContent from `sm:max-w-md` (≈448 px) to
`sm:max-w-2xl` (≈672 px). Still full width on phones, capped on
desktop. Reschedule's existing `grid-cols-1 sm:grid-cols-2
md:grid-cols-3` now has room to actually render three inputs at md
without clipping (Input already defaults to w-full).
- CascadePromptBlock affected-meetings table: switched to
`table-fixed` with an explicit `<colgroup>` (w-20 number / flex
title / w-40 times). The number column was bumped from w-12 to
w-20 so the Arabic header "رقم الاجتماع" + `whitespace-nowrap`
has room at common font sizes (raised during code review). Title
cell keeps its `truncate` + `title=` tooltip but no longer needs
`max-w-[180px]`. Added `whitespace-nowrap` to # and times headers
so the latter never wraps. Wrapper got `overflow-x-hidden` to
belt-and-suspenders the no-horizontal-scroll guarantee. Action
button row already used `flex-wrap` — preserved.
Tests (artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs):
- Added a Reschedule-tab regression test that opens the dialog at
1280×800 in Arabic and asserts the bounding boxes of
`reschedule-date` / `reschedule-start` / `reschedule-end` all sit
inside the dialog's bounding box (1 px tolerance).
- Added a Cascade-table regression test (raised during code review)
that seeds a primary + 2 followers, postpones to fire the cascade
prompt, and asserts the `cascade-followers-list` and the last
`<td>` (times) of the first follower row both sit inside the
dialog at desktop width in RTL.
- Confirmed no behavior regressions across the postpone-by-10,
cascade follower display, tab-switching, and reschedule-to-tomorrow
tests.
Out of scope (per task spec): backend cascade/postpone logic, the
floating alert panel itself, dialog visual restyling, and the Cancel
tab beyond what comes "for free" with the wider dialog.
The pre-existing `test` workflow failures (executive-meetings reorder,
font-settings roundtrip, notes-share recipient PATCH 403/404) are
unrelated to this layout change and were already failing before.
|
||
|
|
21bac11aa5 |
#485 Fix overlapping layout in the Postpone meeting dialog
In Arabic/RTL the Postpone dialog (opened from the upcoming-meeting
alert) was visibly overlapping its own borders on two tabs:
1. Postpone tab → cascade preview ("الاجتماعات المتأثرة"): the times
column ran wider than the pink panel, the title column pushed it
off-screen, and the meeting-# header wrapped to two lines while
the others stayed on one.
2. Reschedule tab: three native date / start / end inputs forced into
md:grid-cols-3 inside an sm:max-w-md dialog, which clipped the
right-most input against the dialog frame in RTL.
Both issues were pure layout — the dialog was simply too narrow for
its content.
Changes (artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx):
- Bumped the postpone DialogContent from `sm:max-w-md` (≈448 px) to
`sm:max-w-2xl` (≈672 px). Still full width on phones, capped on
desktop. Reschedule's existing `grid-cols-1 sm:grid-cols-2
md:grid-cols-3` now has room to actually render three inputs at md
without clipping (Input already defaults to w-full).
- CascadePromptBlock affected-meetings table: switched to
`table-fixed` with an explicit `<colgroup>` (w-12 number / flex
title / w-40 times). Title cell keeps its `truncate` + `title=`
tooltip but no longer needs `max-w-[180px]`. Added `whitespace-nowrap`
to # and times headers so the latter never wraps. Wrapper got
`overflow-x-hidden` to belt-and-suspenders the no-horizontal-scroll
guarantee. Action button row already used `flex-wrap` — preserved.
Tests (artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs):
- Added a regression test that opens the dialog at 1280×800 in
Arabic, switches to the Reschedule tab, and asserts the bounding
boxes of `reschedule-date` / `reschedule-start` / `reschedule-end`
all sit inside the dialog's bounding box (1 px tolerance).
- Re-ran the full upcoming-alert spec to confirm no behavior
regressions in postpone-by-minutes, cascade prompts, cancel,
reschedule-to-tomorrow, dismiss, or the alert position-clamp test.
Out of scope (per task spec): backend cascade/postpone logic, the
floating alert panel itself, dialog visual restyling, and the Cancel
tab beyond what comes "for free" with the wider dialog.
The pre-existing `test` workflow failures (executive-meetings reorder,
font-settings roundtrip, notes-share recipient PATCH 403/404) are
unrelated to this layout change and were already failing before.
|
||
|
|
2b31b5e3aa |
Remove title field from notes and update tests to reflect changes
Refactors the notes feature by removing the `title` field and updating all related tests and UI components to use `content` instead. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7464969c-726a-4ec2-a3b2-8ff63f91f6ed Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw Replit-Helium-Checkpoint-Created: true |
||
|
|
a8db38c70a |
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: 684521f6-b89f-4401-ba07-ccbceb33161d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh Replit-Helium-Checkpoint-Created: true |
||
|
|
d6b19748f7 |
Update upcoming meeting alert to better display cascade information
Refactor upcoming meeting alert component to update the cascade prompt table structure and adjust end-to-end tests to reflect the changes in column count and content assertions. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 9509e0a2-e6ad-4b70-8d04-56b729a8f67c Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh Replit-Helium-Checkpoint-Created: true |
||
|
|
7ea7744e26 |
#481 Polish cascade-affected meetings table in postpone/reschedule prompt
- Drop the leading "#" index column; dailyNumber already serves as a stable per-row identifier. - Replace the hard-coded amber background/border with the user's chosen alert accent via hexToRgba(accent, 0.12 / 0.45). Threaded `accent` through PostponeDialog → CascadePromptBlock so children don't read prefs directly. Both the loading and main prompt blocks track the accent; the rose blocked-by-midnight variant is intentionally kept. - Render times as localized 12-hour with ص/م (AR) or AM/PM (EN) by reusing the shared formatTime helper instead of slicing "HH:mm". - Drop now-unused i18n key cascadeColIndex from ar.json + en.json. - Update the e2e test to match the 3-column schema (meeting#, title, times); kept the dailyNumber assertion in the first cell. tsc clean. Cascade specs pass (Postpone by 10, Reschedule cascade, Cascade prompt UI: Shift/Keep, no-followers fallthrough). |
||
|
|
f429b57cf3 |
Improve meeting alert accessibility by using semantic table headers
Update table header cells in the upcoming meeting alert component to use `<th>` elements with `scope="row"` for improved accessibility, while maintaining visual styling. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: cfc95efc-e616-467b-a131-64907741bd55 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh Replit-Helium-Checkpoint-Created: true |
||
|
|
5e37d392c0 |
#479: Render upcoming-alert attendees as numbered tables per group
- DetailsPanel in upcoming-meeting-alert.tsx: replaced the per-group
`<ul class="list-disc">` with a real `<table class="w-full">`. Each
attendee row is `<tr>` with two `<td>`s: a narrow tabular-nums index
cell (`{idx + 1}.`, scope="row") and the cleaned name cell. The
group heading is rendered as `<caption>` (text-start, font-bold,
text-xs) so it spans both columns and is announced as the table
caption to AT. Each table also carries `aria-label={group.heading}`.
- Numbering restarts at 1 per group (each group is its own table); a
code comment notes the one-line tweak to use a continuous count.
- Preserved: `data-testid="alert-details-attendees"` wrapper,
`max-h-40 overflow-y-auto` scroll, empty-state, location/URL rows,
and the section header total ("الحضور (n)"). RTL/LTR work via
`text-start` and `pe-1`.
- e2e: added a new test in
`artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs`
that seeds a meeting with two subheading-delimited groups (3
external + 1 internal), expands details, and asserts two tables
render with rows starting at "1." in each group.
Verification:
- `pnpm exec tsc --noEmit` clean.
- New attendees-table spec passes (1/1, 16.5s).
No server, schema, or i18n key changes; Tailwind utilities only.
|
||
|
|
ef00f48d23 |
Update translations for cascade meeting list notes
Add and update localization strings for the cascade meeting list, including a note about `cascadeListItem` being intentionally kept for backward compatibility. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7ea15d6b-48ed-4f23-a920-ac6569ce3a81 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh Replit-Helium-Checkpoint-Created: true |
||
|
|
ae31ed90c6 |
#480 Show cascade-affected meetings as a numbered table
Replace the simple <ul> in the cascade-prompt panel inside the upcoming- meeting alert with a 4-column table: row index, the meeting's dailyNumber, title (truncate + title=), and "from → to". The new `meetingNumbersById` map is built once in UpcomingMeetingAlert from the already-loaded dayData and threaded through PostponeDialog into both CascadePromptBlock call sites (postpone-minutes and reschedule). Existing `cascade-followers-list` and `cascade-follower-<id>` testids are preserved on the table/rows so prior selectors keep matching. Adds 4 new i18n keys (cascadeColIndex/MeetingNumber/Title/Times) in ar.json and en.json. The legacy cascadeListItem key is left in place since it isn't worth a separate cleanup. Extends the existing "Cascade prompt UI: Shift sends cascadeFollowing" e2e test to assert the first row's index cell renders "1" and the meeting-number cell renders the seeded follower's dailyNumber. insertImminentMeeting now also returns dailyNumber so the assertion can read it. tsc clean; targeted cascade specs pass (3/3). |
||
|
|
62e7be4c42 |
alert: keep meeting reminder buttons clickable while a Dialog is open
Task #478. When the upcoming-meeting reminder ("الاجتماع التالي") appeared on top of an open note thread dialog, the user could not tap "تأجيل" / "تم" / × / "تفاصيل". The buttons looked enabled and the popup sat above the dialog overlay (`z-[60]` over `z-50`), but nothing happened on click. Root cause: Radix `Dialog` is modal by default and its scroll-lock helper (`react-remove-scroll`) sets `pointer-events: none` on the document body while open. The meeting alert is portaled to body and inherited that, silently swallowing all clicks. Fix: in `artifacts/tx-os/src/components/executive-meetings/upcoming- meeting-alert.tsx`, add `pointer-events-auto` to the floating panel's className so it explicitly opts back in to receiving pointer events. Buttons inside inherit auto from the panel. Inline comment added referencing #478 and the Radix scroll-lock behavior. No change to the shared `Dialog` primitive, no other tokens or components touched. Existing `#282` behavior (hide alert while its own postpone modal is open) is preserved. Verified: - `tsc --noEmit` clean. - `notes-thread-dialog.spec.mjs` passes (no regression in the scenario that surfaced the bug). |
||
|
|
951fcce26b |
theme: lighten primary navy from #0B1E3F to #2C4A8A
Task #477. The brand primary color was a very dark navy (218 70% 15% ≈ #0B1E3F) and dominated buttons, the active sidebar row, the FAB, and focus rings. User asked to lighten it while keeping the same blue family. Change: in `artifacts/tx-os/src/index.css` (`:root`), bumped four tokens to `218 55% 32%` (≈ #2C4A8A) and updated the inline hex comment: - `--primary` - `--ring` - `--sidebar-primary` - `--sidebar-ring` White text on the new primary keeps WCAG AA contrast (≈5.3:1), so the existing `--primary-foreground: 0 0% 100%` is preserved. No other tokens (background, foreground, accent, secondary, destructive, borders) were touched. No component-level overrides were modified. |
||
|
|
76f2f7ce51 |
notes(ipad): keep dialog and inputs above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new note or replying inside the note thread dialog covered the bottom of the dialog (textarea + send button). The dialog used `top:50%; translate(-50%,-50%)` against the layout viewport and never reacted to the visual viewport shrinking when the keyboard appeared. Changes: - artifacts/tx-os/index.html: added `interactive-widget=resizes-content` to the viewport meta so iOS 17+ resizes the layout viewport when the software keyboard opens. - artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that subscribes to `window.visualViewport` `resize` / `scroll` / `orientationchange` (rAF-throttled) and exposes the visible height, the visual viewport `offsetTop`, and the bottom keyboard inset. - artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now reads the hook and, only when the keyboard inset exceeds ~80px, pins its center to `offsetTop + height/2` and caps `max-height` to the visible height. With the existing `translate(-50%,-50%)`, both edges stay inside the visible region above the keyboard. Desktop unaffected (inset is 0 → no inline style applied). - artifacts/tx-os/src/pages/notes.tsx: thread reply textarea and the top composer textarea now call `scrollIntoView({ block: "center" })` on focus and again whenever the keyboard inset changes while the field is focused. Both paths are gated on `keyboardInset > 80`, so desktop focus is untouched. Verification: - `tsc --noEmit` clean. - e2e: `notes-thread-dialog` and `notes-composer-color` both pass. |
||
|
|
db6e7726eb |
notes(ipad): keep dialog above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new note or replying inside the note thread dialog covered the bottom of the dialog (textarea + send button). The dialog used `top:50%; translate(-50%,-50%)` against the layout viewport and never reacted to the visual viewport shrinking when the keyboard appeared. Changes: - artifacts/tx-os/index.html: added `interactive-widget=resizes-content` to the viewport meta so iOS 17+ resizes the layout viewport when the software keyboard opens. - artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that subscribes to `window.visualViewport` `resize` / `scroll` / `orientationchange` (rAF-throttled) and exposes the visible height, the visual viewport `offsetTop`, and the bottom keyboard inset. - artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now reads the hook and, only when the keyboard inset exceeds ~80px (i.e. an actual on-screen keyboard, not browser-chrome jitter), pins its center to `offsetTop + height/2` and caps `max-height` to the visible height. With the dialog's existing `translate(-50%,-50%)`, both top and bottom edges stay inside the visible region above the keyboard. Desktop renders unchanged (inset is 0 → no inline style applied beyond what the caller passes). Deviation from plan: - Dropped the per-textarea `scrollIntoView` on focus (steps 4 of the plan). Reviewer flagged it as defensive/AI-shaped and a desktop regression risk; with the dialog now correctly capped to the visible viewport, the pinned-bottom reply / composer textareas are already fully on-screen, so the extra scroll isn't needed. Verification: - `tsc --noEmit` clean. - `notes-thread-dialog` e2e still passes. |
||
|
|
5dac662421 |
notes(ipad): keep dialog above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new note or replying inside the note thread dialog covered the bottom of the dialog (textarea + send button). The dialog used `top:50%; translate(-50%,-50%)` against the layout viewport and never reacted to the visual viewport shrinking when the keyboard appeared. Changes: - artifacts/tx-os/index.html: added `interactive-widget=resizes-content` to the viewport meta so iOS 17+ resizes the layout viewport when the software keyboard opens. - artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that subscribes to `window.visualViewport` `resize` / `scroll` / `orientationchange` events (rAF-throttled) and exposes the current visible height plus the bottom keyboard inset. - artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now reads the hook and, when the keyboard inset is non-trivial (> 80px), pins its center to the visible viewport mid-line and caps `max-height` to the visible height. Desktop renders unchanged (inset is 0 → no inline style applied beyond what the caller passes). - artifacts/tx-os/src/pages/notes.tsx: the thread dialog reply textarea and the top composer textarea now `scrollIntoView({ block: "center" })` on focus (after a 250ms delay so the keyboard animation settles), so the cursor lands inside the visible region on touch devices. Desktop focus is unaffected — `scrollIntoView` is a no-op when the element is already in the viewport. Verification: - `tsc --noEmit` clean. - Existing `notes-thread-dialog` e2e still passes (dialog stays capped at 85% viewport height, scroller still overflows, recipient chips and composer remain in viewport). |
||
|
|
36f6d1f5e6 |
notes(shared): show Send button on notes inside shared folders
User report (AR, RH on iPad): no Send button visible inside a shared "desk" folder. The shared-folder view was passing `hideSend` to every NoteCard for editors and rendered no card actions at all for read-only viewers, so nobody could send a note from a shared folder. Changes (artifacts/tx-os/src/pages/notes.tsx, SharedFolderView): - Editors: removed `hideSend` from the NoteCard so the existing Send affordance (and color/labels/archive/delete) appears as it does on the user's own notes. - Read-only viewers: added a small Send icon button to the static card (testid `shared-folder-note-send-<id>`) so they can also send the note via the existing send dialog. The send endpoint authorizes off the authenticated caller, so the note is sent under RH's identity (no server change needed). |
||
|
|
d0e907e409 |
notes(folders): allow drag-drop of notes onto shared folders (edit perm)
User report (AR): "shared folders also appear here — the user should
be able to move notes wherever they want." The folders rail already
lists shared-with-me folders, but only OWN folder rows registered as
@dnd-kit drop targets. Recipients with edit permission could see a
shared folder but couldn't drop a note onto it.
Changes:
- folders-rail.tsx: extend the inline `DroppableRow` to accept an
optional `ownerId`. Wrap each shared-folder row in a DroppableRow
when `sf.myPermission === "edit"`, passing the owning user's id;
read-only viewers stay non-droppable. The droppable id is namespaced
with `-shared-<ownerId>` so it never collides with the owner-side
row for the same folder id.
- notes.tsx: extend `sharedFolderBucketRef` to carry the bucket's
`ownerId` (sourced from `data.folder.ownerId`). In `handleDragEnd`,
read `o.ownerId` from the drop data:
• undefined → own folder / Unfiled. Allow only own notes.
• number → shared folder. Allow only when the source note
came from sharedBucket and `sharedBucket.ownerId
=== o.ownerId`.
This mirrors the server-side rule that an editor may move notes
only between folders owned by the same owner — cross-owner drops
are blocked client-side so we never round-trip a 400.
Out of scope:
- Cross-owner moves (would require a copy/share flow, not supported
by the server today).
- Drop targets on shared folders inside the FoldersBrowser tile grid
(the rail covers the user's reported screenshot; tiles can follow).
|