479fa00ecc96b575cac136dc2236952a3ec7f415
232 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
479fa00ecc |
Add automated tests for assigning permissions to a role (Task #107)
Adds artifacts/api-server/tests/role-permissions-assign.test.mjs covering
the previously-uncovered role permission-assignment endpoints in
artifacts/api-server/src/routes/roles.ts:
- PUT /api/roles/:id/permissions
- Replaces an existing set (verifies returned body and the row set in
role_permissions in the DB).
- System-role guard: returns 400 with code "system_role_permissions"
and leaves the system role's permissions unchanged.
- Unknown permission id: returns 404 and the rejected PUT does NOT
partially apply (DB set unchanged).
- POST /api/roles/:id/permissions
- Adds a single permission, returns 201, and is idempotent on repeat
add (no duplicate row, still 201).
- System-role guard: 400 with code "system_role_permissions",
DB unchanged.
- Unknown permission id: 404, DB unchanged.
- DELETE /api/roles/:id/permissions/:permissionId
- Removes the permission and returns 204; idempotent on second delete.
- Idempotent (204, not 404) for an unknown permission id; the role's
other permissions are not collaterally removed.
- System-role guard: 400 with code "system_role_permissions",
DB permissions for system role unchanged.
Style mirrors tests/roles-crud.test.mjs and role-permission-audit.test.mjs:
test admin user provisioned in `before`, login via /api/auth/login, full
cleanup of created roles/users in `after`. Tests run via the standard
`pnpm --filter @workspace/api-server test` command.
Verified all 8 new tests pass against the running API server. The two
pre-existing failures in app-permissions-unique.test.mjs are unrelated
and already tracked by a separate task.
Replit-Task-Id: 885dca7a-421d-4be1-993b-299bfd0719b4
|
||
|
|
9b3e320c78 |
Add Playwright tests for the My Orders cancel/delete undo toast
Original task (#103): cover the 7-second "Undo" action that appears after cancelling or deleting an order on /my-orders, since the existing order-place-cancel spec only verified the post-cancel state and never exercised the undo path. What was added: - New spec at artifacts/tx-os/tests/order-undo-toast.spec.mjs with two cases: 1. English: place a fresh pending order via the services modal, cancel it from the in-card confirm dialog, click "Undo" inside the toast within the 7-second window, then assert the restore PATCH /api/orders/:id/status succeeds, the timeline + Cancel button reappear in the card, and the DB row is back to "pending". 2. Arabic: seed a "completed" order directly in the DB, click the trash icon + "نعم، احذف", then click "تراجع" in the toast. Verifies that NO DELETE /api/orders/:id ever fires (page-level request listener), the order row still exists in the DB after the original 7s window has elapsed, and the card stays visible with the مكتمل status pill. - Both English and Arabic toast labels are exercised, satisfying the bilingual requirement. Implementation notes / deviations: - The spec is automatically picked up by the existing test:e2e config (testMatch: /.*\.spec\.mjs$/), so no playwright.config or package.json changes were needed. - DB seeding/cleanup mirrors the conventions in artifacts/tx-os/tests/order-place-cancel.spec.mjs (same bcrypt hash for TestPass123!, same afterAll teardown of orders/notifications/user_roles/users). - While wiring the test I discovered the shadcn Toaster's <li> root has no role="status" on this Radix version, so role-based selectors miss it. The spec uses `li[data-state="open"]` (Radix's data attribute) scoped to the most recent toast — explained in a comment in the file. Verification: - pnpm exec playwright test tests/order-undo-toast.spec.mjs → 2 passed. - Re-ran together with the existing order-place-cancel spec → 4 passed, no regressions. Follow-ups proposed (#157, #158): bulk "Clear N finished" + undo coverage, and the unmount-flushes-pending-deletes path. Replit-Task-Id: bd1c5067-e994-4299-b3a6-855ffb45ed71 |
||
|
|
43aa0c72a0 |
Task #152: cell-merge + current-meeting tint on EM schedule
Adds two related features to the executive-meetings schedule: 1. Current-meeting tinting — meeting/attendees/time cells of the row matching the current time get a soft wash of the user's highlight color (~13% alpha), keeping their original text color. The # cell keeps its existing strong-fill behavior (highlightColor + white text) when current, so the row anchor stays visually distinct. 2. Cell-merge overlay — editors can merge "meeting+attendees", "meeting+attendees+time", or the whole row into a single free-text cell. Stored in the DB so all viewers see the same overlay; the originals (titleAr/En, attendees, start/endTime) stay untouched and are restored on Unmerge. Implementation notes - DB: 3 nullable columns added to executive_meetings (merge_start_column, merge_end_column, merge_text). Applied via direct ALTER TABLE because pnpm --filter db run push is blocked by the pre-existing app_permissions duplicate (Task #148, surfaced as follow-up #156). Schema file kept in sync. - API: meetingPatchSchema accepts an optional `merge` field (`null | {start,end,text}`) with start<=end ordering and server-side sanitizeRichText on the text. Reuses the existing PATCH route. - Frontend: new MergeMenu component + canonical-order range resolution so merges still render when boundary columns are hidden, and gracefully degrade to unmerged rendering (without losing DB state) when column reordering makes the visible span non-contiguous. Unmerge stays available whenever a stored merge exists, even in degraded state, and existing mergeText is preserved on re-merge. Trigger has the same touch-pointer visibility classes (opacity-40 on coarse pointer + larger tap target) used by the row grip handle. - i18n: en + ar keys under executiveMeetings.merge.* - Realtime: new emitExecutiveMeetingsDayChanged() helper broadcasts `executive_meetings_changed` with `{ date }` after PATCH commits; the notifications-socket hook subscribes and invalidates the affected day's query so other open tabs reflect merge edits without a manual refresh. Out of scope (deferred to follow-ups) - API + e2e test coverage for the merge endpoint and UI flows (#154) - Realtime emission for the remaining EM mutations — POST/DELETE/ PUT/duplicate/reorder (#155) Validation - TypeScript compiles cleanly across the changed surfaces (existing pre-existing errors in api-zod / generated client are unrelated). - API tests: 108/110 pass — the only failures are the pre-existing app_permissions-unique cases (Task #148, follow-up #156). - Architect code review: PASS after addressing the # cell strong-fill, touch-pointer trigger visibility, and realtime broadcast issues flagged in the prior validation pass. |
||
|
|
539f11e25d |
Task #152: cell-merge + current-meeting tint on EM schedule
Adds two related features to the executive-meetings schedule: 1. Current-meeting tinting — meeting/attendees/time cells of the row matching the current time get a soft wash of the user's highlight color (~13% alpha), keeping their original text color and the existing colored ring. The # cell stays solid white/red. 2. Cell-merge overlay — editors can merge "meeting+attendees", "meeting+attendees+time", or the whole row into a single free-text cell. Stored in the DB so all viewers see the same overlay; the originals (titleAr/En, attendees, start/endTime) stay untouched and are restored on Unmerge. Implementation notes - DB: 3 nullable columns added to executive_meetings (merge_start_column, merge_end_column, merge_text). Applied via direct ALTER TABLE because pnpm --filter db run push is blocked by the pre-existing app_permissions duplicate (Task #148, surfaced as the new follow-up #156). Schema file kept in sync. - API: meetingPatchSchema accepts an optional `merge` field (`null | {start,end,text}`) with start<=end ordering and server-side sanitizeRichText on the text. Reuses the existing PATCH route. - Frontend: new MergeMenu component + canonical-order range resolution so merges still render when boundary columns are hidden, and gracefully degrade to unmerged rendering (without losing DB state) when column reordering makes the visible span non-contiguous. Unmerge stays available whenever a stored merge exists, even in degraded state, and existing mergeText is preserved on re-merge. - i18n: en + ar keys under executiveMeetings.merge.* Out of scope (deferred to follow-ups #154 / #155, per task brief) - API + e2e test coverage for the merge endpoint and UI flows - Realtime emission for executive-meetings mutations (no existing emit on this surface; covered by #155) Validation - TypeScript compiles cleanly across api-server + tx-os. - API tests: 108/110 pass — the 2 failures are the pre-existing app_permissions-unique cases (Task #148, follow-up #156), unrelated to this change. - Architect code review approved after addressing hidden-column fallback, non-contiguous reorder degradation, and Unmerge-availability edge cases. |
||
|
|
2ffd63832a |
Add e2e tests for the receiver-side incoming-orders flow
Task #102: Adds Playwright UI coverage for the receiver side of the service-orders flow, mirroring the existing API-level matrix in artifacts/api-server/tests/service-orders.test.mjs. What's added - artifacts/tx-os/tests/order-receiver-flow.spec.mjs with two tests: 1) "receiver claims, prepares, completes — requester sees live status updates": seeds a requester (role "user") and a receiver (role "order_receiver"), runs each in its own browser context, has the requester place an order via /services, then has the receiver claim it from /orders/incoming and walk it through Mark Preparing → Mark Completed. After every receiver action it asserts that the requester's /my-orders status pill ("Received", "Preparing", "Completed") updates without a manual refresh — exercising the order_updated socket.io push wired through use-notifications-socket. 2) "receiver cancels after claiming — requester sees Cancelled pill": covers the receiver-side cancel path required by the task by claiming the order and then cancelling from /orders/incoming, and verifies the requester's card flips to the red "Cancelled" pill and loses the timeline step circles. Test design notes - English UI only; the language matrix is already covered for the requester side by order-place-cancel.spec.mjs. - Each test creates a unique requester display name so that incoming cards can be uniquely located by service name + "From <display name>" even if other cards exist on the page. - Status assertions use exact-text matching on the pill to avoid the "Received" / "Awaiting receiver" substring overlap. - afterAll cleans up service_orders, notifications, user_roles and the seeded users (defensive: also deletes any orders referencing those users that weren't explicitly tracked). Other - The new spec is automatically picked up by the existing `pnpm --filter @workspace/tx-os test:e2e` run (testMatch in artifacts/tx-os/playwright.config.mjs already globs *.spec.mjs). - Verified locally: full tx-os e2e suite (9 specs) passes. Replit-Task-Id: bdbee3d1-20e0-4a40-b2b1-51ea16a15bb7 |
||
|
|
1eb5ca11fb |
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: 2c56ba37-edcd-4e0b-b6e1-4b185ddb982f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/n3Cg2XG Replit-Helium-Checkpoint-Created: true |
||
|
|
10a48657c7 |
Push role permission changes to signed-in users in real time
Original task (#101): When an admin saves the role editor, role holders need their permission-driven UI (admin nav items, permission-gated action buttons, etc.) to refresh without waiting for a page reload. The server already emitted `apps_changed`, but that event is semantically about visible apps, not the broader cached permission state, and the client only invalidated the apps list and `/api/auth/me`. Changes: - artifacts/api-server/src/lib/realtime.ts - Extracted role-holder resolution (direct user_roles + indirect via group_roles -> user_groups) into a private helper. - Added `emitRolePermissionsChangedToHolders(roleId)` that emits a new `role_permissions_changed` socket event with `{ roleId }` to every holder of the role. - artifacts/api-server/src/routes/roles.ts - After `PUT /api/roles/:id/permissions` succeeds, call the new emitter alongside the existing `emitAppsChangedToRoleHolders`. - artifacts/tx-os/src/hooks/use-notifications-socket.ts - Subscribed to `role_permissions_changed`. Invalidates `getMe`, `listRoles`, `listPermissions`, and—when the payload includes the role id—`getRolePermissions`, the role permission audit, and role usage queries so admin-only UI re-evaluates without a refresh. Scope notes / deviations: - The task spec called out `PUT /api/roles/:id/permissions` only, so the matching POST/DELETE single-permission endpoints still emit only `apps_changed`. Filed as follow-up #150 to keep them consistent. - No new automated tests added (filed as follow-up #151). Pre-existing typecheck errors in artifacts/api-server/src/routes/executive-meetings.ts are unrelated and untouched. - `replit.md` not updated; this is a behavioral refinement of an existing socket flow, not an architectural change. Replit-Task-Id: bd31a9a9-dbf1-497a-8505-96672f92bc79 |
||
|
|
cdacb079e9 | Git commit prior to merge | ||
|
|
a14b589006 |
Audit role permission changes (Task #100)
Record an audit trail every time a role's permissions change and surface recent history inside the role edit dialog. Schema (lib/db): - New `role_permission_audit` table: id, roleId (FK roles), actorUserId (FK users, nullable on delete), previousPermissionIds int[], newPermissionIds int[], createdAt. Created via raw SQL because `drizzle push` currently fails on a pre-existing app_permissions duplicate (followup #148 tracks fixing this). API (artifacts/api-server): - PUT /api/roles/:id/permissions now wraps the permission delete/insert, the legacy audit_logs row, and the new role_permission_audit row in a *single* transaction so the permission set and its audit trail always commit (or roll back) together. Previous IDs are also read inside the transaction to avoid races. - A role_permission_audit row is written on EVERY PUT call (per task spec), including no-op saves; the GET handler computes addedPermissionIds/removedPermissionIds so the History UI can distinguish meaningful changes from no-op saves. - New GET /api/roles/:id/audit (admin-only, ?limit=1..50, default 10) returns recent entries newest-first with computed added/removedPermissionIds and joined actor info. - New tests in tests/role-permission-audit.test.mjs cover write, every-call write (incl. no-op), GET ordering+diff+actor, no-op diff reporting, 404, and limit. Drive-by fixes: - Fixed pre-existing syntax corruption in tests/apps-open.test.mjs (stray `ccaPassa,` line from a prior bad merge that prevented the whole api-server test workflow from running). - Made tests/roles-crud.test.mjs "rejects an invalid name" robust to parallel test execution by scoping the leak check to the bad name instead of relying on a global row count. API spec / codegen (lib/api-spec, lib/api-client-react): - Added `getRolePermissionAudit` operation and `RolePermissionAuditEntry` schema; ran orval codegen. Frontend (artifacts/tx-os): - New RolePermissionHistory component renders inside the role edit dialog (between permissions and Save/Cancel) using useGetRolePermissionAudit. Shows timestamp, actor, added/removed permission names (resolved via permissionsById), and total count. - Save invalidates the audit query so the new entry appears immediately on the next open. - Bilingual i18n strings (en + ar with full Arabic plural variants: zero/one/two/few/many/other) under admin.roles.history*. Verification: - tx-os typecheck passes. - All 5 new audit tests + 13 existing roles tests pass. - E2E (testing skill) verified the full flow: empty state on a fresh role, save adds a permission, reopened dialog shows the new entry with actor name in Arabic. Docs: - replit.md updated to list `role_permission_audit` in Database Tables. Follow-ups proposed: #146 paginate/filter history, #147 audit other admin permission changes, #148 fix drizzle push duplicate. Replit-Task-Id: 9b8886a2-6e76-4072-b345-a772421fa161 |
||
|
|
19b5dc788e |
Task #141: Make schedule row reordering work on iPad/touch devices
Original task: Fix iPad/touch drag-to-reorder of meeting rows in the
Executive Meetings daily schedule. Two root causes identified in the
spec:
1. dnd-kit was wired with PointerSensor only, so iOS Safari claimed
the touch as a vertical scroll before the 6px activation distance
was reached and the drag never started.
2. The grip handle was opacity-0 group-hover:opacity-70 — invisible
on touch devices that have no hover state, so iPad users had no
affordance to discover the drag.
Changes (all in artifacts/tx-os/src/pages/executive-meetings.tsx):
- Imported TouchSensor from @dnd-kit/core and added it to useSensors
with activationConstraint { delay: 200, tolerance: 8 } as the spec
explicitly required. PointerSensor (distance: 6) and KeyboardSensor
are unchanged.
- Updated the row grip button className with arbitrary Tailwind
media-query variants:
[@media(hover:none)_and_(pointer:coarse)]:opacity-40
[@media(hover:none)_and_(pointer:coarse)]:group-hover:opacity-40
[@media(hover:none)_and_(pointer:coarse)]:p-1.5
Desktop hover-only behaviour (opacity-0 → group-hover:opacity-70 →
hover:opacity-100) is preserved.
- Enlarged the GripVertical icon on touch (w-3.5/h-3.5 → w-4/h-4)
for a friendlier tap target.
- touch-action:none stays only on the grip button itself; the rest
of the row keeps default touch-action so vertical page scroll
outside the handle still works.
Verified manually on a 768x1024 hasTouch context:
- getComputedStyle(grip).opacity = "0.4"
- matchMedia('(hover:none) and (pointer:coarse)').matches = true
- touch-action on grip = "none"
- title cell (td:nth-child(2)) has no role and no aria-roledescription
— confirms drag listeners are still scoped to the grip only and
desktop click-to-edit on title/attendee/time cells is preserved.
Code review: PASS (architect).
Follow-up proposed: #149 — add a Playwright mobile/touch e2e test
covering long-press + drag of the grip on /executive-meetings.
Pre-existing test workflow failure (api-server tests + admin.tsx
codegen drift from earlier tasks) is unrelated and out of scope.
|
||
|
|
c372430a2c |
Task #141: Make schedule row reordering work on iPad/touch devices
Original task: Fix iPad/touch drag-to-reorder of meeting rows in the
Executive Meetings daily schedule. Two root causes identified in the
spec:
1. dnd-kit was wired with PointerSensor only, so iOS Safari claimed
the touch as a vertical scroll before the 6px activation distance
was reached and the drag never started.
2. The grip handle was opacity-0 group-hover:opacity-70 — invisible
on touch devices that have no hover state, so iPad users had no
affordance to discover the drag.
Changes (all in artifacts/tx-os/src/pages/executive-meetings.tsx):
- Imported TouchSensor from @dnd-kit/core and added it to useSensors
with activationConstraint { delay: 250, tolerance: 5 } — matches
the values already used in home.tsx so touch behaviour is
consistent across the OS. PointerSensor (distance: 6) and
KeyboardSensor are unchanged.
- Updated the row grip button className with arbitrary Tailwind
media-query variants:
[@media(hover:none)_and_(pointer:coarse)]:opacity-40
[@media(hover:none)_and_(pointer:coarse)]:group-hover:opacity-40
[@media(hover:none)_and_(pointer:coarse)]:p-1.5
Desktop hover-only behaviour (opacity-0 → group-hover:opacity-70 →
hover:opacity-100) is preserved.
- Enlarged the GripVertical icon on touch (w-3.5/h-3.5 → w-4/h-4)
for a friendlier tap target.
- touch-action:none stays only on the grip button itself; the rest
of the row keeps default touch-action so vertical page scroll
outside the handle still works.
Verified manually on a 768x1024 hasTouch context:
- getComputedStyle(grip).opacity = "0.4"
- matchMedia('(hover:none) and (pointer:coarse)').matches = true
- touch-action on grip = "none"
- title cell (td:nth-child(2)) has no role and no aria-roledescription
— confirms drag listeners are still scoped to the grip only and
desktop click-to-edit on title/attendee/time cells is preserved.
Code review: PASS (architect).
Follow-up proposed: #149 — add a Playwright mobile/touch e2e test
covering long-press + drag of the grip on /executive-meetings.
Pre-existing test workflow failure (api-server tests + admin.tsx
codegen drift from earlier tasks) is unrelated and out of scope.
|
||
|
|
3aa6e81ed7 |
Show users/groups impact before removing role permissions (Task #99)
Adds a per-permission impact preview to the role editor so admins can see
who would lose each permission before saving — and a confirmation step
when the change would actually revoke access from at least one user.
API
- New endpoint POST /api/roles/:id/permissions/impact-preview
- Body: { permissionIds: number[] } (the proposed kept set)
- Response: { removed: [{ permissionId, permissionName, userCount,
groupCount, groups: [{id,name}] }], totalAffectedUsers }
- A user is counted as "affected" only when no other role they hold
(direct or via groups) still grants the removed permission.
- Implemented in artifacts/api-server/src/routes/roles.ts using two
drizzle selectDistinct queries (direct + via groups) merged in JS.
Switched away from a sql`${arr}::int[]` approach that failed because
drizzle expands JS arrays as a parameter list, not as a single array
parameter. Now uses inArray().
OpenAPI / client
- Added schemas RolePermissionsImpactBody, RolePermissionImpactGroup,
RolePermissionImpactItem, RolePermissionsImpact in
lib/api-spec/openapi.yaml.
- Regenerated lib/api-client-react/src/generated/* (new
usePreviewRolePermissionsImpact hook).
UI
- artifacts/tx-os/src/pages/admin.tsx RolesPanel:
- Debounced (350ms) on-demand fetch only when at least one permission
has been unchecked relative to the saved state.
- Inline amber summary panel listing each removed permission with
user/group counts, plus a total-affected-users line.
- Confirmation modal before save when totalAffectedUsers > 0.
- Added EN/AR translations (impactTitle, impactPerPermission,
impactViaGroups, impactTotal, confirmRemoval*) using i18next
_one/_other plural keys to match existing pluralized strings.
Tests
- New artifacts/api-server/tests/role-permissions-impact.test.mjs
(6 tests, all green): empty removals, group-derived impact,
"covered by another role" exclusion, 404 on unknown role, 400 on
invalid body, 403 for non-admin.
- Existing roles-crud tests still pass.
- E2E flow verified end to end via the testing tool: amber summary
appears, confirmation dialog gates the destructive save, and the
resulting permission set persists.
Code-review follow-ups (applied in this same task)
- Tightened the OpenAPI 404 description for the new endpoint to clarify
that unknown permission IDs in the body are tolerated (only a missing
role yields 404). Regenerated the API client.
- Added a request-sequencing guard in the role editor so a stale
in-flight impact-preview response cannot overwrite a newer selection.
- When the impact preview fails while removals exist, the inline panel
now shows an explicit error message and the Save button is disabled
until the user resolves it (e.g. by changing the selection so the
preview can re-run successfully). EN/AR translations added under
admin.roles.impactError.
Notes
- The pre-existing test workflow failure (ECONNREFUSED on 127.0.0.1:8080
before the API server is ready) is unrelated and already tracked.
- replit.md not updated — change is endpoint-level and does not alter
documented architecture.
- A modification to artifacts/tx-os/public/opengraph.jpg may show up in
the diff; it is a build artifact regenerated by the vite dev server
on restart and is not part of this change.
Replit-Task-Id: 30b7a2f7-7b60-429a-89d4-79280a81803f
|
||
|
|
3634769117 |
Improve tap target size for attendee elements on touch devices
Adjusted CSS for attendee elements to increase tap target size by adding padding and min-height, improving usability on touch devices like iPads. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 54d6551d-3f41-47b7-aad2-35c81e5d9b06 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/n3Cg2XG Replit-Helium-Checkpoint-Created: true |
||
|
|
b5de44485e |
Task #140: 3 attendees-cell refinements in Executive Meetings schedule
(1) Clearing an attendee's name now removes the attendee row instead of
saving an empty record. saveAttendeeName detects empty stripped HTML
(tags + + whitespace) and PUTs the array with the row removed
and sortOrder repacked.
(2) Inline "+ Add attendee" button next to each attendee group, plus
"+ Virtual / + Internal / + External" chips for groups not yet present
in a split cell. Implemented as an optimistic ghost row — clicking +
sets a single global pendingAttendee state and renders a synthetic
EditableCell at the end of the matching group. Only when the user
types a non-empty name and blurs do we PUT the new attendee. Empty
blur or Escape just clears the pending state — no orphan rows reach
the API. The API stays strict (name.min(1)).
(3) Always-visible dashed underline on each attendee EditableCell
makes the multi-attendee wrapped layout discoverable as click targets;
hidden in edit mode (data-[editing=true]) and on print. The group
label ("— Virtual attendance — Webex —") is now pointer-events:none
so clicks pass through to whatever sits underneath instead of being
swallowed by the header text.
Implementation notes:
- EditableCell gained two new props (forceSaveOnBlur, onCancel) so the
ghost row can commit/discard cleanly even when the editor is empty.
- pendingAttendee is a single global state (only one ghost open at a
time across the whole page). All other rows hide their + buttons
while a ghost is open to prevent the user from orphaning typing.
- Locale keys added in ar.json + en.json under
executiveMeetings.schedule.{addAttendee, addVirtualAttendee, …,
newAttendeeAria, editAttendeeAria}.
- Backend executive-meetings.ts unchanged — attendeeSchema still
requires non-empty name.
Verified end-to-end:
- Add attendee, commit on blur, delete by clearing, cancel by Escape
on empty.
- Cross-row gating: opening a ghost in row A hides "+" buttons in
row B until the ghost is committed/cancelled.
- Webex/virtual header click no longer enters edit mode.
Pre-existing TypeScript errors in admin.tsx (codegen drift from #96)
and 3 failing api-server tests are unrelated and out of scope.
Follow-ups proposed: #144 (inline edit attendee titles),
#145 (move attendees between virtual/internal/external groups).
|
||
|
|
e5e96e7022 |
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: eab87fb0-38a8-43ae-95c6-49598fc45ab4 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/NvUGneQ Replit-Helium-Checkpoint-Created: true |
||
|
|
eaba29095e |
Task #137: Polish Executive Meetings schedule table (5 refinements + post-review fixes)
Original 5 refinements:
1. Time displayed on a single line (TimeRangeCell with whitespace-nowrap).
2. Time inline-editable with two HH:MM inputs.
3. Reliable attendee inline-edit with placeholder + dir handling.
4. Floating formatting toolbar via portal so it never clips the table.
5. Inline "Add row" tr at the bottom of the table.
Follow-up fixes from architect reviews:
- Add row scrolls the new row into view (requestAnimationFrame +
scrollIntoView center) and auto-opens the title cell in edit mode
(createRow captures POST response id, threads autoEditTitleId through
ScheduleSection -> MeetingRow -> EditableCell.startInEditMode).
autoEditTitleId hardened with a 1.5s fallback clear so it can't go
stale if the title cell never mounts.
- Time editor explicit Save/Cancel buttons (Check/X icons, em-time-save-/
em-time-cancel- testids, onMouseDown preventDefault to survive blur).
- Invalid time order (start > end) raises a destructive toast with
i18n key executiveMeetings.schedule.timeOrderError (AR + EN), keeps
the editor open, and refocuses the start input. Equal start/end is
valid per spec. Server errors also toasted.
- Toolbar always placed BELOW the editing cell (no flip-above) so it
never sits on top of the row above the active cell.
- Toolbar horizontally clamped inside the table's scroll container
(nearest overflowX:auto/scroll ancestor), not just the viewport, so
it never escapes the table visually.
- Add-row label uses spec-mandated copy ("+ Add meeting" /
"+ أضف اجتماعًا جديدًا") via i18n key.
- Removed duplicate error toast on time save: TimeRangeCell now only
rolls back local state in its catch; the parent saveTimes is the
single source for surfacing the destructive error toast.
Files touched:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- artifacts/tx-os/src/components/editable-cell.tsx
- artifacts/tx-os/src/locales/{ar,en}.json
Verification:
- E2E (Playwright): all 5 features + post-review fixes pass.
* Toolbar measured BELOW the cell at top and bottom of viewport
(delta ~4px, isBelow=true in both cases).
* Toolbar measured INSIDE the table scroll container's left/right.
* Equal start/end (14:00–14:00) saved without validation toast.
- Manual API: POST 201, PATCH 200, validation 400, DELETE 204.
- Tasks #122 and #125 still functional.
- Pre-existing failing tests (admin.tsx codegen drift from #96 +
3 api-server tests) are unrelated and untouched.
|
||
|
|
1ba05c11d0 |
Task #137: Polish Executive Meetings schedule table (5 refinements + post-review fixes)
Original 5 refinements:
1. Time displayed on a single line (TimeRangeCell with whitespace-nowrap).
2. Time inline-editable with two HH:MM inputs.
3. Reliable attendee inline-edit with placeholder + dir handling.
4. Floating formatting toolbar via portal so it never clips the table.
5. Inline "Add row" tr at the bottom of the table.
Follow-up fixes from architect reviews:
- Add row scrolls the new row into view (requestAnimationFrame +
scrollIntoView center) and auto-opens the title cell in edit mode
(createRow captures POST response id, threads autoEditTitleId through
ScheduleSection -> MeetingRow -> EditableCell.startInEditMode).
autoEditTitleId hardened with a 1.5s fallback clear so it can't go
stale if the title cell never mounts.
- Time editor explicit Save/Cancel buttons (Check/X icons, em-time-save-/
em-time-cancel- testids, onMouseDown preventDefault to survive blur).
- Invalid time order (start > end) raises a destructive toast with
i18n key executiveMeetings.schedule.timeOrderError (AR + EN), keeps
the editor open, and refocuses the start input. Equal start/end is
valid per spec. Server errors also toasted.
- Toolbar always placed BELOW the editing cell (no flip-above) so it
never sits on top of the row above the active cell.
- Toolbar horizontally clamped inside the table's scroll container
(nearest overflowX:auto/scroll ancestor), not just the viewport, so
it never escapes the table visually.
Files touched:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- artifacts/tx-os/src/components/editable-cell.tsx
- artifacts/tx-os/src/locales/{ar,en}.json
Verification:
- E2E (Playwright): all 5 features + post-review fixes pass.
* Toolbar measured BELOW the cell at top and bottom of viewport
(delta ~4px, isBelow=true in both cases).
* Toolbar measured INSIDE the table scroll container's left/right.
* Equal start/end (14:00–14:00) saved without validation toast.
- Manual API: POST 201, PATCH 200, validation 400, DELETE 204.
- Tasks #122 and #125 still functional.
- Pre-existing failing tests (admin.tsx codegen drift from #96 +
3 api-server tests) are unrelated and untouched.
|
||
|
|
df28f4f0d4 |
Task #137: Polish Executive Meetings schedule table (5 refinements + 4 follow-up fixes)
Original 5 refinements:
1. Time displayed on a single line (TimeRangeCell with whitespace-nowrap).
2. Time inline-editable with two HH:MM inputs.
3. Reliable attendee inline-edit with placeholder + dir handling.
4. Floating formatting toolbar via portal so it never clips the table.
5. Inline "Add row" tr at the bottom of the table.
Follow-up fixes from architect review:
- Add row now scrolls the new row into view (requestAnimationFrame +
scrollIntoView center) and auto-opens the title cell in edit mode
(createRow captures POST response id, threads autoEditTitleId through
ScheduleSection -> MeetingRow -> EditableCell.startInEditMode).
- Time editor explicit Save/Cancel buttons (Check/X icons, em-time-save-/
em-time-cancel- testids, onMouseDown preventDefault to survive blur).
- Invalid time order (end <= start, including equal times) now raises a
destructive toast with i18n key executiveMeetings.schedule.timeOrderError
(AR + EN), keeps the editor open, and refocuses the start input.
Server errors also toasted.
- autoEditTitleId state hardened with a 1.5s fallback clear so it can't go
stale if the title cell never mounts (e.g., column hidden).
- Toolbar prefers placement BELOW the editing cell (only flips above
when no room below in viewport), so it never covers the row above.
Files touched:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- artifacts/tx-os/src/components/editable-cell.tsx
- artifacts/tx-os/src/locales/{ar,en}.json
Verification:
- E2E (Playwright): all 5 features + 4 fixes pass; toolbar prefer-below
measured cell.bottom=276.84 / toolbar.top=280.84 on a top-of-viewport
row (correctly flips above only at viewport bottom).
- Manual API: POST 201, PATCH 200, validation 400, DELETE 204.
- Tasks #122 and #125 still functional.
- Pre-existing failing tests (admin.tsx codegen drift from #96 +
3 api-server tests) are unrelated and untouched.
|
||
|
|
a045445759 |
Task #137: Five refinements to the Executive Meetings schedule table
Original ask (Arabic): make the time fit on one line, let users edit
the time inline, make inline-editing of attendee names reliable
(including for empty names), keep the formatting toolbar from
covering the table, and add an inline "Add row" button at the bottom
of the table.
Changes
-------
1) Time on a single line + inline-editable
- artifacts/tx-os/src/pages/executive-meetings.tsx
* New `TimeRangeCell` component renders "HH:MM – HH:MM" on a
single line with `whitespace-nowrap`.
* Click / Enter / Space enters edit mode with two side-by-side
`<input type="time">` controls (start / end).
* Saves on blur (debounced) or Enter; Esc cancels and restores
the previous values.
* Client-side guard: refuses to save when start > end (server
also returns a 400 with a validation error).
* `saveTimes` callback wires to PATCH /api/executive-meetings/:id
with `{startTime, endTime}` (null clears).
* Time column default width bumped 120 → 200 to keep one line at
the default zoom.
2) Inline edit attendee names (incl. empty names)
- artifacts/tx-os/src/pages/executive-meetings.tsx (AttendeeFlow)
* EditableCell for attendee names now uses `placeholder="…"`,
`dir="auto"`, and a min-width / padding so an empty name is
still a visible click target and respects RTL/LTR.
3) Toolbar that doesn't cover the table
- artifacts/tx-os/src/components/editable-cell.tsx
* `FormattingToolbar` now renders through `createPortal` to
`document.body` with `position: fixed`.
* Position is recomputed via `getBoundingClientRect` with
above/below flip and horizontal viewport clamp; tracked by
`ResizeObserver` plus scroll/resize listeners.
* The shared toolbar ref is consulted in `onFocusOut` and a new
document `pointerdown` listener so clicks inside the toolbar
no longer close the editor.
4) Inline "Add row" at the bottom of the table
- artifacts/tx-os/src/pages/executive-meetings.tsx
* New `<tr data-testid="em-add-row-tr">` rendered after the
sortable rows, gated on `canMutate && !isLoading`, with
`print:hidden`.
* `createRow` callback POSTs to /api/executive-meetings with
bilingual default titles ("اجتماع جديد" / "New meeting") for
the currently selected `meetingDate`.
5) i18n
- artifacts/tx-os/src/locales/{ar,en}.json:
* Added `executiveMeetings.schedule.{addRow,
newMeetingDefaultAr, newMeetingDefaultEn, timeStart, timeEnd}`
in both locales.
Verification
------------
- Manual API: POST creates row (201), PATCH valid times (200), PATCH
invalid (start > end) returns the validation 400, PATCH null
clears times, DELETE returns 204.
- Browser E2E (Playwright): all five refinements verified end-to-end
on /executive-meetings. The agent's own cleanup step initially
reported a stale row but the row was actually deleted server-side
(verified by 404 on direct GET).
- Architect review: PASS, no blockers; constraints (Tasks #122/#125,
no TeaBoy references, bilingual i18n) all satisfied.
Note
----
- artifacts/tx-os/public/opengraph.jpg was inadvertently touched
earlier in the session; restored to its pre-build state in this
commit. No real change to the asset.
|
||
|
|
59c7338d63 |
Task #98: Warn admins before renaming a role used in code references
- Added GET /api/roles/:id/usage endpoint returning {userCount, groupCount},
guarded by requireAdmin and validating the role id.
- Updated PATCH /api/roles/:id audit metadata to record renamed=true,
renamedFrom, and renamedTo when the role name changes (in addition to
existing name fields), satisfying the traceability requirement.
- Added RoleUsage schema + /roles/{id}/usage operation to openapi.yaml,
regenerated lib/api-zod via orval. Codegen script in lib/api-spec was
updated intentionally to preserve `export * from './manual'` in
api-zod's index, which orval was overwriting.
- RolesPanel edit dialog (admin.tsx) now tracks the original role name
on open and shows an amber warning banner with from/to text plus a
usage line (users + groups counts) whenever the trimmed name input
differs from the original. Warning is suppressed for system or
protected roles since their name input is disabled.
- Added en/ar locale strings: renameWarningTitle, renameWarningBody,
renameUsage.
- Reverted accidental opengraph.jpg change picked up earlier in the
session — it is unrelated to this task.
Verification: e2e test created a non-system role, assigned the admin
user to it, opened the edit dialog, observed the warning + usage line,
saved the rename, and confirmed both the role name persisted and the
audit log captured renamed/renamedFrom/renamedTo.
Note: The pnpm `test` workflow shows pre-existing failures in
executive-meetings.ts that are unrelated to this task (verified via
git stash before changes).
Replit-Task-Id: c1ee048d-50c6-4439-a430-ee0b7ec09959
|
||
|
|
0d58a856d6 | Git commit prior to merge | ||
|
|
768d6eab4c |
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: c18e1a47-f2d8-412d-86fc-05ad16b0f23a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/9GxtwFd Replit-Helium-Checkpoint-Created: true |
||
|
|
896f690ac0 | Git commit prior to merge | ||
|
|
ed52b845f1 |
Task #125: Restore meetings table on mobile, allow pinch-zoom
Reverts the phone-only stacked-card layout introduced in Task #119 back to a single tidy table for every viewport, and re-enables pinch-zoom on the whole app. Changes: - artifacts/tx-os/index.html: viewport meta no longer pins maximum-scale=1, so iOS/Android pinch gestures work everywhere (now: width=device-width, initial-scale=1). - artifacts/tx-os/src/pages/executive-meetings.tsx: * Removed the md:hidden cards branch (the entire em-schedule-cards block) so the table is the only schedule view at every width. * Dropped the `hidden md:block print:block` gating on the table container; it now renders at all viewports inside the existing overflow-x-auto wrapper, so a too-wide table scrolls horizontally inside its own container instead of breaking the page layout. * Deleted the now-orphaned MeetingCard component and its only consumer of the inline-only RowColorPickerInline variant. * Deleted RowColorPickerInline (no remaining consumer); the hover RowColorPicker is still used by table rows. Preserved: - Desktop (≥xl) fixed-width + resizable column behavior is unchanged (table-fixed at xl, resize handles still mouse-only). - Print fixed-layout behavior (`print:table-fixed`) and RTL support are unchanged. - Localized labels for the customizer/highlight popover are unchanged. Verification: - pnpm --filter @workspace/tx-os exec tsc --noEmit shows no new errors in executive-meetings.tsx (pre-existing admin.tsx errors from the just-merged Task #96 codegen drift are unrelated). - The table renders at 390 / 768 / 1280 px widths; horizontal scroll appears only when the table is wider than the container. Out of scope (per task spec): - Print/PDF page (executive-meetings-print.tsx) — Task #120. - API, schema, columns, or RBAC — none changed. |
||
|
|
c0cf2115dd |
Task #96: Show dependency counts in admin app/service/user lists
Goal: make the admin delete dialog show its dependency warning on
the FIRST click (matching the existing Groups UX), instead of
needing a 409 round-trip from the DELETE endpoint to populate the
warning.
Changes:
- lib/api-spec/openapi.yaml: added optional count fields to App
(groupCount, restrictionCount, openCount), Service (orderCount),
and UserProfile (noteCount, orderCount, conversationCount,
messageCount).
- artifacts/api-server/src/routes/apps.ts: GET /admin/apps now
batch-loads groupCount/restrictionCount/openCount via grouped
COUNT queries on group_apps, app_permissions, and app_opens.
- artifacts/api-server/src/routes/services.ts: GET /services now
batch-loads orderCount from service_orders.
- artifacts/api-server/src/routes/users.ts: GET /users now batch-
loads noteCount/orderCount/conversationCount/messageCount from
notes, service_orders, conversations.created_by, and
messages.sender_id.
- artifacts/tx-os/src/pages/admin.tsx: Apps, Services, and Users
delete buttons pre-populate their conflict-state from the row's
count fields so the DeletionWarningDialog shows the warning on
the first click. The lazy 409 fallback still works as a safety
net for any caller without counts.
- replit.md: documented the new admin-panel delete UX.
Tests:
- Added artifacts/api-server/tests/list-dependency-counts.test.mjs
with 6 tests verifying each list endpoint exposes the new count
fields with the expected non-zero values when dependents exist
AND zero values when they do not (apps, services, users).
- Existing artifacts/api-server/tests/delete-force-warnings.test.mjs
(9 tests) still passes — DELETE behavior unchanged.
- Verified end-to-end with a Playwright browser test: clicking the
service delete icon ONCE opens the confirmation modal with the
dependency warning ("orders", count >= 1) immediately.
Notes / drift:
- Count fields were added to the shared App/Service/UserProfile
schemas (not list-only sub-schemas) so the same shape is returned
from list and detail endpoints. Code review flagged this as
acceptable but broader than strictly necessary; left as is to
keep the API consistent.
Out of scope (already pre-existing on main, tracked as follow-ups):
- artifacts/api-server/tests/apps-open.test.mjs has a syntax error.
- artifacts/api-server/tests/app-permissions-unique.test.mjs has
two failing assertions about the composite primary key.
- artifacts/api-server/src/routes/executive-meetings.ts has 3 pre-
existing typecheck errors around the font-settings scope column.
- The `test` workflow exits with ECONNREFUSED when the api-server
isn't already up — needs a readiness check.
Replit-Task-Id: bd14fe73-9961-431b-ab5a-ab70f116e8c7
|
||
|
|
03ba2cf99f |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
a759e0eb90 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
aaca31e3c8 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
e891b9a057 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
f19e781741 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
7bfac1d834 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
25a3ef21d2 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
ee1e2a9f42 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
7cc0c73a2e |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
242f3f06c4 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
ca1267948b |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
4539933aee |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
da18dc6ecf |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
93c77f587c |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
9ec0fc1f90 |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, font-family, font-size, and text-align controls (Tiptap-backed EditableCell + FormattingToolbar). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint permutes daily_number + start/end times in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint and survives partial-day reorders. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage (em-current-meeting-highlight-v1). Backend changes - Widened titleAr / titleEn / attendees.name to text. - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, reorder, and applyApprovedRequest (add_attendee) paths so rich text round-trips safely. - Code-review fixes: duplicate handler re-sanitizes titleAr/titleEn; applyApprovedRequest add_attendee sanitizes the inserted name; reorder transaction return value cleaned (`byId` removed). - 5 new tests cover sanitization stripping, reorder happy path, cross-day rejection, permission denial, and applyApprovedRequest sanitization. Pre-existing app_permissions failures are unrelated (tracked by #126/#127). Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar (custom FontSize Tiptap extension + @tiptap/extension-text-align). - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/external. - Print page renders sanitized rich text via dangerouslySetInnerHTML on names and titles; attendee.title is rendered as a plain text node (XSS). - Translation keys added in ar.json and en.json. |
||
|
|
5114b207da |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, and font controls (Tiptap-backed EditableCell). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint swaps daily_number in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage. Backend changes - Widened titleAr / titleEn / attendees.name to text (applied via direct ALTER TABLE because db:push --force is currently blocked by Task #126). - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, and reorder paths so rich text round-trips safely. - Code-review fix: duplicate handler now re-sanitizes titleAr/titleEn. - Code-review fix: print page no longer interpolates the unsanitized attendee.title into dangerouslySetInnerHTML. - 4 new tests cover sanitization stripping, reorder happy path, cross-day rejection, and permission denial. Pre-existing app_permissions failures are unrelated and tracked by Task #126. Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar. - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell now passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/ external sections. - Print page renders sanitized HTML via dangerouslySetInnerHTML on names and titles (titles still rendered as plain text on the print page). - Translation keys added in ar.json and en.json. Drift - Sanitization for attendee.title was identified by the architect review as a defense-in-depth gap and proposed as Task #130 rather than fixing inline; the print-page XSS path that depended on it was fixed directly. |
||
|
|
7a2f91d262 |
Improve test reliability by adding cookie assertions and refining user cleanup
Add assertion for `connect.sid` cookie in login helper and refine user deletion in `executive-meetings-visibility.test.mjs` to only remove users from the current test run. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 25d8531b-a2fe-4c98-b988-638cc149da80 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/RZuzKd2 Replit-Helium-Checkpoint-Created: true |
||
|
|
19200140c1 |
Task #121: Hide Executive Meetings icon from non-executive users
Gate the home-screen "Executive Meetings" app icon behind a new
`executive_meetings.access` permission so only admins and the five
executive_* roles see it. Reuses the existing `app_permissions` →
`getVisibleAppsForUser` machinery; no route or middleware changes.
Changes
- scripts/src/seed.ts
* Added permission `executive_meetings.access`.
* Granted it to: admin, executive_ceo, executive_office_manager,
executive_coord_lead, executive_coordinator, executive_viewer.
* Linked it to apps.slug = 'executive-meetings' via app_permissions.
* All inserts use onConflictDoNothing — re-running the seeder is safe.
- artifacts/api-server/scripts/gate-executive-meetings.mjs (new)
* One-shot, idempotent pg migration that performs the same three
inserts in a single transaction (BEGIN/ROLLBACK on error). Prints
JSON summary with grant counts. Already executed against dev DB
(newRoleGrantsThisRun=6, newAppLinksThisRun=1).
- artifacts/api-server/tests/executive-meetings-visibility.test.mjs (new)
* Regression: creates a fresh `order_receiver`-only user, asserts
/api/apps does NOT include `executive-meetings`; then grants
`executive_coordinator` and asserts it does. Per-run username
prefix + defensive sweep + after-hook cleanup.
Out of scope (not modified)
- artifacts/api-server/src/routes/apps.ts (getVisibleAppsForUser)
- artifacts/api-server/src/routes/executive-meetings.ts
(requireExecutiveAccess)
- Any UI files
Verification
- `node artifacts/api-server/scripts/gate-executive-meetings.mjs` →
ok=true, 6 role grants, 1 app link.
- `node --test tests/executive-meetings-visibility.test.mjs` →
2/2 pass.
- TypeScript clean for scripts package.
- Architect review: PASS, no blocking issues.
|
||
|
|
3994ccdb0c |
Task #120 — Make the Executive Meetings print page responsive (no clipped attendee names)
Problem:
artifacts/tx-os/src/pages/executive-meetings-print.tsx built the schedule
as one monolithic <table> with fixed percentage widths (8/32/40/20), no
min-width / overflow guard, no word-break rules, and joined every
attendee into a single comma-separated string. On viewports narrower
than ~900px (and especially on phones) long Arabic attendee names
extended past the cell border and got clipped — the bug shown in the
user's screenshot.
Approach (artifacts/tx-os/src/pages/executive-meetings-print.tsx only):
1. Vertical attendee list (T1):
- Replaced the joined-string Attendees cell with one <div> per
attendee inside a flex-column .em-print-attendees wrapper.
Numbered "1- Name", "2- Name (Title)" etc. — same prefix style
as the on-screen page (AttendeeFlow), kept simple per the task
plan (no grouping by attendance type — that's on-screen UX).
- Added data-testid="em-print-attendees-<meetingId>" so tests can
assert the vertical list rendered.
2. Cell wrap protections (T1):
- .em-print-table th, .em-print-table td now set
word-break: break-word; overflow-wrap: anywhere; white-space:
normal — so even very long single tokens break inside the cell
instead of overflowing.
3. Screen scroll guard + responsive widths (T2):
- Wrapped the <table> in a div.em-print-scroll with
overflow-x:auto, -webkit-overflow-scrolling:touch.
- Table given min-width:640px so it stays readable on small
screens; if the viewport is narrower, the wrapper scrolls
horizontally instead of breaking the page layout.
- Rebalanced column widths from 8/32/40/20 to 6/30/44/20 — gives
attendees the most room since long names dominate the cell.
4. Print-mode overrides (T2):
- @media print resets .em-print-scroll overflow to visible,
.em-print-table min-width to 0, and th:first-child width to
auto — so A4 printing keeps today's layout, the # column can
shrink to its natural width, and there's no scroll behavior
bleeding into print.
5. Scope hygiene (T3):
- Did NOT touch executive-meetings.tsx — that on-screen schedule
is owned by the separately-tracked Task #119 (already merged)
and the new Task #125 (restore table on mobile + pinch-zoom).
The diff for this task is exactly one file plus this commit
message.
Verification (testing skill — Playwright):
- Desktop 1280x720: page renders, attendees shown as separate <div>
children, no horizontal page scroll, td styles use white-space:
normal + word-break:break-word.
- Phone 360x800: no horizontal PAGE scroll; the .em-print-scroll
wrapper carries any overflow internally; attendees are vertical
divs and each <div> wraps within the cell (scrollWidth ≤
clientWidth + 2px tolerance).
- Tablet 768x1024: no horizontal page scroll (viewport ≥ table
min-width).
- Print emulation (tablet + phone): .em-print-scroll computed
overflow-x === "visible", .em-print-table computed min-width ===
"0px" — A4 layout preserved.
- Architect review: PASS, no blocking issues.
Out of scope (per task brief):
- Real PDF generation (Task #111).
- The on-screen schedule layout (Tasks #119 / #125).
- Data model, columns, theme, RBAC.
|
||
|
|
be4ecb30bb |
Task #95: Let admins export the audit log as CSV
What was done - Added a new admin-only endpoint GET /api/admin/audit-logs/export that streams the filtered audit log as a CSV download. Honors the same `action`, `from`, and `to` query parameters as the list endpoint, validated through a shared parseFilters/buildWhere helper extracted from the existing handler. - Columns: action, actor_username, target_type, target_id, created_at, metadata (compact JSON). Rows ordered newest-first and capped at 50,000 to bound response size. UTF-8 BOM prepended so spreadsheet apps (incl. Excel) detect encoding correctly for Arabic content stored in metadata. - Response sets Content-Type: text/csv; charset=utf-8, Content-Disposition: attachment; filename="audit-log-YYYY-MM-DD.csv", Cache-Control: no-store, and is written via res.write streaming. - Updated lib/api-spec/openapi.yaml with operationId exportAuditLogsCsv and ran codegen to regenerate the React client + Zod schemas. - Frontend (artifacts/tx-os/src/pages/admin.tsx → AuditLogPanel): added an "Export CSV" button next to the existing filter actions. Clicking it fetches the export URL with current filters using same-origin cookies, triggers a browser download, and surfaces a localized error if the request fails. The button is disabled while filters are invalid or while a download is in flight, and a spinner replaces the icon during export. - Added bilingual translation keys admin.audit.export.button / admin.audit.export.failed in en.json and ar.json. Security hardening (post code-review) - csvEscape now prefixes a single quote when a cell value begins with one of =, +, -, @, tab, or CR, mitigating CSV/spreadsheet formula injection (Excel, LibreOffice, Google Sheets evaluate such cells as formulas). Verified end-to-end by inserting a synthetic audit row whose action started with "=" and confirming the export wrote it as "'=...". Verification - Typecheck of tx-os passes; api-server has only pre-existing executive-meetings errors unrelated to this change. - Manual curl smoke test: 200 with correct headers, BOM present, action and date filters honored, invalid date returns 400. - E2e UI test (Playwright via testing skill): logged in as admin, opened the Audit Log section, downloaded the unfiltered CSV (correct filename and header row), then re-exported with action=app.delete and confirmed only matching rows came back. - Targeted security check confirmed CSV injection mitigation (see above). Notes / drift - None for the spec. The generated `exportAuditLogsCsv()` helper in lib/api-client-react is typed Promise<Blob> but, because customFetch auto-infers text/csv as text, would currently return text if anyone called it directly. The Audit Log UI uses a raw fetch with credentials: "include" to download the blob, so this does not affect the feature. Updating the generated client's responseType handling is project-wide config and is intentionally out of scope here. Follow-ups proposed - #123 Add automated tests for the audit log CSV export (test_gaps) - #124 Let admins pick which columns to include when exporting the audit log (next_steps) Replit-Task-Id: 88a50100-caa7-4b37-b9da-cfdc42bee119 |
||
|
|
30ea1facd3 |
Improve how resize handles are displayed on the executive meetings page
Adjust logic to hide resize handles on touch devices and non-large screens. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 78b3f620-f6d4-4b0b-82c3-1928d988a31a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/RZuzKd2 Replit-Helium-Checkpoint-Created: true |
||
|
|
5b35261785 |
Task #119 — Make the meetings schedule table fit phones and tablets
Problem:
The Executive Meetings schedule table used `tableLayout: "fixed"` with
hard-coded column widths totalling ~1100px (number 56 + meeting 320 +
attendees 600 + time 120). On any phone or iPad portrait this forced a
horizontal scrollbar and clipped attendee text.
Approach (artifacts/tx-os/src/pages/executive-meetings.tsx):
1. Added a small useMediaQuery hook (matchMedia listener with cleanup,
guarded for SSR).
2. ScheduleSection now renders TWO views with CSS-based switching so
nothing depends on JS state for visibility:
- Mobile cards container: className "md:hidden print:hidden",
data-testid="em-schedule-cards". Stacked cards with number badge,
title/location, time, and attendees. Same row-color background.
- Tablet/desktop table container: "hidden md:block print:block".
Always shown for print regardless of viewport.
3. Within the table:
- className now "table-auto xl:table-fixed print:table-fixed".
Tailwind classes alone control whether widths are enforced.
- <colgroup> + per-cell widths are emitted unconditionally so the
same widths apply at xl screens AND in print at any viewport. In
`table-auto` mode (md..<xl screens), browsers treat them as
hints and shrink columns to fit, so no horizontal scroll.
- Resize handles are screen-only (`isXl &&`); they need viewport-
driven layout to work and would just block touch scrolling.
4. MeetingRow now always provides width + overflow:hidden inline.
With break-words on the inner divs, wrapping behavior remains
correct under both layout modes.
5. New MeetingCard component for the mobile layout: number badge,
title/location, time, attendees in a stacked layout, with the
row-color background and a touch-friendly always-visible color
picker (RowColorPickerInline). The Columns customizer still gates
which fields appear via visibleColumns.some(...).
6. Refactored RowColorPicker into a shared RowColorPickerSwatches
body plus two trigger variants:
- RowColorPicker: original hover-only absolute trigger for the
table number cell (testid em-row-color-trigger).
- RowColorPickerInline: always-visible trigger for cards (testid
em-row-color-trigger-inline).
Verification (testing skill):
- Screen layout at 1280, 1440, 768, 390 px: PASS
- Desktop: table visible, em-schedule-cards hidden.
- Tablet 768: table visible, NO horizontal scroll on body.
- Phone 390: cards visible, table hidden, NO horizontal scroll.
- Print fidelity at sub-xl viewports (768 and 390): PASS
- getComputedStyle(table).tableLayout = "fixed" (print:table-fixed
takes effect at any viewport).
- First column header width ≈ 56px (configured "number" width)
confirms colgroup widths are honored in print at narrow widths.
- Print-only header visible, table visible (print:block),
em-schedule-cards hidden (print:hidden).
- First architect review caught a print-fidelity regression
(previous draft gated colgroup on isXl); fixed by removing all
isXl gating from the widths and letting CSS classes alone govern
layout mode. Re-tested and verified.
Out of scope (per task brief):
- The dedicated print artifact (executive-meetings-print.tsx) —
owned by Task #120.
- Data model, API, columns, theme/colors.
|
||
|
|
3bbaa98747 |
Task #119 — Make the meetings schedule table fit phones and tablets
Problem:
The Executive Meetings schedule table used `tableLayout: "fixed"` with
hard-coded column widths totalling ~1100px (number 56 + meeting 320 +
attendees 600 + time 120). On any phone or iPad portrait this forced a
horizontal scrollbar and clipped attendee text.
Approach (artifacts/tx-os/src/pages/executive-meetings.tsx):
1. Added a small useMediaQuery hook (matchMedia listener with cleanup,
guarded for SSR).
2. ScheduleSection now renders TWO views with CSS-based switching so
nothing depends on JS state for visibility:
- Mobile cards container: className "md:hidden print:hidden",
data-testid="em-schedule-cards". Stacked cards with number badge,
title/location, time, and attendees. Same row-color background.
- Tablet/desktop table container: "hidden md:block print:block".
Always shown for print regardless of viewport.
3. Within the table:
- className now "table-auto xl:table-fixed print:table-fixed".
Tailwind classes alone control whether widths are enforced.
- <colgroup> + per-cell widths are emitted unconditionally so the
same widths apply at xl screens AND in print at any viewport. In
`table-auto` mode (md..<xl screens), browsers treat them as
hints and shrink columns to fit, so no horizontal scroll.
- Resize handles are screen-only (`isXl &&`); they need viewport-
driven layout to work and would just block touch scrolling.
4. MeetingRow now always provides width + overflow:hidden inline.
With break-words on the inner divs, wrapping behavior remains
correct under both layout modes.
5. New MeetingCard component for the mobile layout: number badge,
title/location, time, attendees in a stacked layout, with the
row-color background and a touch-friendly always-visible color
picker (RowColorPickerInline). The Columns customizer still gates
which fields appear via visibleColumns.some(...).
6. Refactored RowColorPicker into a shared RowColorPickerSwatches
body plus two trigger variants:
- RowColorPicker: original hover-only absolute trigger for the
table number cell (testid em-row-color-trigger).
- RowColorPickerInline: always-visible trigger for cards (testid
em-row-color-trigger-inline).
Verification (testing skill):
- Screen layout at 1280, 1440, 768, 390 px: PASS
- Desktop: table visible, em-schedule-cards hidden.
- Tablet 768: table visible, NO horizontal scroll on body.
- Phone 390: cards visible, table hidden, NO horizontal scroll.
- Print fidelity at sub-xl viewports (768 and 390): PASS
- getComputedStyle(table).tableLayout = "fixed" (print:table-fixed
takes effect at any viewport).
- First column header width ≈ 56px (configured "number" width)
confirms colgroup widths are honored in print at narrow widths.
- Print-only header visible, table visible (print:block),
em-schedule-cards hidden (print:hidden).
- First architect review caught a print-fidelity regression
(previous draft gated colgroup on isXl); fixed by removing all
isXl gating from the widths and letting CSS classes alone govern
layout mode. Re-tested and verified.
Out of scope (per task brief):
- The dedicated print artifact (executive-meetings-print.tsx) —
owned by Task #120.
- Data model, API, columns, theme/colors.
|
||
|
|
abe6f6ab1d |
Task #119 — Make the meetings schedule table fit phones and tablets
Problem:
The Executive Meetings schedule table used `tableLayout: "fixed"` with
hard-coded column widths totalling ~1100px (number 56 + meeting 320 +
attendees 600 + time 120). On any phone or iPad portrait this forced a
horizontal scrollbar and clipped attendee text.
Approach (artifacts/tx-os/src/pages/executive-meetings.tsx):
1. Added a small useMediaQuery hook (matchMedia listener with cleanup,
guarded for SSR).
2. ScheduleSection now renders TWO views with CSS-based switching so
nothing depends on JS state for visibility:
- Mobile cards container: className "md:hidden print:hidden",
data-testid="em-schedule-cards". Stacked cards with number badge,
title/location, time, and attendees. Same row-color background.
- Tablet/desktop table container: "hidden md:block print:block".
Always shown for print regardless of viewport width.
3. Within the table:
- className now "table-auto xl:table-fixed print:table-fixed".
- <colgroup> with pixel widths only emitted when isXl (or SSR).
- <th width style> only applied when isXl.
- Resize handles only rendered when isXl (touch users at <xl get
no col-resize handles, so they don't fight scrolling).
4. MeetingRow gained an `applyFixedWidths` prop. When false, it drops
per-cell width and overflow:hidden so cells wrap freely. break-words
was already present on title/location.
5. New MeetingCard component for mobile: number badge, title +
location, time, attendees in a stacked layout, with row color
background and a touch-friendly always-visible color picker
(RowColorPickerInline). The Columns customizer still gates which
fields appear in the cards via visibleColumns.some(...).
6. Refactored RowColorPicker into a shared RowColorPickerSwatches body
plus two trigger variants:
- RowColorPicker: original hover-only absolute trigger for the
table number cell (testid em-row-color-trigger).
- RowColorPickerInline: always-visible trigger for cards (testid
em-row-color-trigger-inline).
Verification:
- Tested with the testing skill at 1280, 1440, 768, and 390 px.
- Desktop (1280/1440): table visible, em-schedule-cards hidden.
- Tablet (768): table visible, no horizontal scroll on body.
- Phone (390): cards visible, table hidden, no horizontal scroll
(body.scrollWidth = 390).
- Architect review: Pass. RTL preserved (cards and table both pass
dir), Columns customizer gates both views, all existing testids
intact (em-row-*, em-col-header-*, em-row-color-*, em-customize-*,
em-schedule-heading), hooks used unconditionally.
Out of scope (per task brief):
- The dedicated print artifact (executive-meetings-print.tsx) — owned
by Task #120.
- Data model, API, columns, theme/colors.
- Architect noted a non-blocking nuance: client-side Cmd+P from a
<xl viewport will keep table-auto column widths because isXl is
false at print time. This is a print-fidelity concern, not a
responsiveness blocker, and the dedicated print artifact (Task
#120) is the proper home for printing concerns.
|
||
|
|
675fef47f1 |
Task #118 — Fix delete-force-warnings.test.mjs after audit-action rename + harden services tests
T1 (audit-action rename in #93): - Updated the user force-delete test to look up audit_logs by action = ANY(['user.delete', 'user.force_delete']). - Updated the app force-delete test to look up audit_logs by action = ANY(['app.delete', 'app.force_delete']). - Used array-of-actions matchers (forward-compatible with the old name in case anything else still emits it). - The service force-delete test was left untouched because artifacts/api-server/src/routes/services.ts still emits 'service.force_delete' (only user/app/group were renamed in #93). T2 (services per-run uniqueness + defensive sweep): - Added module-level SVC_STAMP and SVC_PREFIX = `TestSvc_<stamp>_`. - Renamed the three services-test name_en literals to `${SVC_PREFIX}Clean`, `${SVC_PREFIX}Busy`, `${SVC_PREFIX}Force` so re-running the file no longer collides on services_name_en_unique. - Now pushes every created service id to createdServiceIds (clean + busy + force), not just busy. - Added a defensive sweep in after() that, after the tracked-id cleanup, deletes any leftover services whose name_en starts with SVC_PREFIX, plus their child rows in service_orders. This mirrors the apps-side sweep added in Task #116. Pre-existing pollution cleanup: - Removed the orphan rows left over from the failed 2026-04-28 test runs (services ids 166/168 = 'Clean Svc'/'Force Svc', user ids 4397/4889 = del_force_*) and their child rows. These predate the fix; removing them now keeps the workspace clean. Verification: - pnpm --filter @workspace/api-server node --test tests/delete-force-warnings.test.mjs: 9/9 PASS twice in a row. - Post-run sweep query: 0 leftover apps, 0 leftover services with the test prefixes, 0 leftover admin users. Out of scope (not touched): - Other test files in artifacts/api-server/tests/. - The audit-action rename itself (already merged in #93). - The wider failing `test` workflow — its other failures are unrelated to this file. |