Skip to content

CLUE-525: Edit button appears on the left toolbar for any document the user can edit - #2930

Open
lbondaryk wants to merge 4 commits into
masterfrom
CLUE-525-edit-button-should-appear-on-left-toolbar-when-user-can-edit
Open

CLUE-525: Edit button appears on the left toolbar for any document the user can edit#2930
lbondaryk wants to merge 4 commits into
masterfrom
CLUE-525-edit-button-should-appear-on-left-toolbar-when-user-can-edit

Conversation

@lbondaryk

Copy link
Copy Markdown
Contributor

What & why

The Edit button in the Sort Work left-pane toolbar only appeared for documents the user owned (openDocument?.uid === user.id). It should appear for any document the user can edit — including their own group's document (a group doc created by any member of their group).

This fix had originally been written for CLUE-525 but got folded into the CLUE-550 (Driving Question Board) branch and never landed on master on its own. This PR extracts just the CLUE-525 change onto a clean branch, drops the DQB-specific bits (which belong to CLUE-550), and fixes a reactivity bug in it.

Changes

  1. Edit-button visibility (sort-work-document-area.tsx): show Edit when the doc is the user's own or their own group's document.
  2. Reactivity fix: derive the uid/type/groupId from the reactive document metadata (kept in sync by Firestore listeners) rather than the lazily-fetched full document. Previously, for a group document created by another group member, the full document's groupId could be undefined until it finished loading, so the Edit button stayed hidden until the view was reloaded. The metadata carries these fields reactively, so the button now appears as soon as the doc syncs — no reload. (This mirrors how isDocumentAccessibleToUser/isVisible in this same component already prefers metadata for the same reason.)
  3. Testability: extracted the rule into a pure canEditSortWorkDocument predicate with a truth-table unit test (own doc, own-group doc incl. created by a groupmate, another group's doc, another student's doc, no-group user, both-undefined guard, and the metadata-sourced group-id path that the reactive fix relies on).

Testing

  • Verified manually on localhost: in Sort Work, a group document created by another member of the user's own group now shows the Edit button without a reload; own documents still show Edit; another group's doc and another student's private doc do not.
  • canEditSortWorkDocument unit tests pass (7 cases); tsc + lint clean.

🤖 Generated with Claude Code

Leslie Bondaryk and others added 3 commits July 24, 2026 14:41
… group)

Extract the edit-button visibility fix that had been folded into the CLUE-550
(Driving Question Board) branch. In the Sort Work document area, show the Edit
button not only for the user's own documents but also for their own group's
document (a group doc created by any member of the user's group), so any
document the user can actually edit exposes the Edit affordance.

This is the CLUE-525 change on its own, without the CLUE-550 DQB work it had
been entangled with.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Source showEdit's uid/type/groupId from the document metadata (which is kept in
sync by Firestore listeners) rather than the lazily-fetched full document. For a
group document created by another group member, the full document's groupId can
be undefined until it finishes loading, which previously left the Edit button
hidden until the view was reloaded. The metadata carries these fields reactively,
so the button now appears as soon as the document syncs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pull the Sort Work Edit-button visibility rule into a pure canEditSortWorkDocument
predicate and cover it with a truth table: own doc, own-group doc (incl. created
by another group member), another group's doc, another student's doc, no-group
user, both-undefined guard, and the metadata-sourced group-id path that the
reactive fix relies on. The component now calls the helper with metadata-preferred
fields; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lbondaryk
lbondaryk requested a review from scytacki July 24, 2026 19:09
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 69.33%. Comparing base (6fb6be9) to head (1513972).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
...rc/components/document/sort-work-document-area.tsx 50.00% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (6fb6be9) and HEAD (1513972). Click for more details.

HEAD has 13 uploads less than BASE
Flag BASE (6fb6be9) HEAD (1513972)
cypress-regression 13 0
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #2930       +/-   ##
===========================================
- Coverage   85.00%   69.33%   -15.67%     
===========================================
  Files         957      952        -5     
  Lines       54527    54511       -16     
  Branches    14357    14359        +2     
===========================================
- Hits        46348    37794     -8554     
- Misses       8161    16683     +8522     
- Partials       18       34       +16     
Flag Coverage Δ
cypress-regression ?
cypress-smoke 41.54% <0.00%> (-0.02%) ⬇️
jest 55.84% <90.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cypress

cypress Bot commented Jul 24, 2026

Copy link
Copy Markdown

collaborative-learning    Run #19557

Run Properties:  status check passed Passed #19557  •  git commit 151397208c: CLUE-525: move edit predicate to document-utils as canUserEditDocument
Project collaborative-learning
Branch Review CLUE-525-edit-button-should-appear-on-left-toolbar-when-user-can-edit
Run status status check passed Passed #19557
Run duration 03m 39s
Commit git commit 151397208c: CLUE-525: move edit predicate to document-utils as canUserEditDocument
Committer Scott Cytacki
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 4
View all changes introduced in this branch ↗︎

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Sort Work’s left-pane toolbar logic so the Edit button appears for any document the current user can edit (their own documents and their group’s documents), and makes the visibility reactive by preferring Firestore-synced document metadata over the lazily-fetched full document.

Changes:

  • Added a pure canEditSortWorkDocument() predicate for Sort Work edit-button visibility.
  • Updated SortWorkDocumentArea to compute showEdit from reactive metadata (with full-document fallback).
  • Added a Jest truth-table style unit test suite for the new predicate.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/components/document/sort-work-edit-permission.ts Introduces the canEditSortWorkDocument predicate to centralize “own doc or own-group doc” edit visibility rules.
src/components/document/sort-work-edit-permission.test.ts Adds unit tests covering key permission cases for the new predicate.
src/components/document/sort-work-document-area.tsx Switches Edit-button visibility from “owned only” to the new predicate, preferring reactive metadata fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +47 to +51
it("recognizes an own-group doc purely from the group id (metadata-sourced)", () => {
expect(canEditSortWorkDocument({
docUid: "student-2", docType: GroupDocument, docGroupId: myGroup, userId: me, userGroupId: myGroup
})).toBe(true);
});
Put the "can this user edit this document?" question beside its sibling
"can this user see this document?" (isDocumentAccessibleToUser), which the
Sort Work document area calls two lines above it.

- Move src/components/document/sort-work-edit-permission.ts into
  src/models/document/document-utils.ts and rename the export to
  canUserEditDocument. The rule isn't Sort-Work-specific.
- Take { document, documentMetadata, user } instead of five loose
  primitives, mirroring isDocumentAccessibleToUser. This pulls the
  metadata-preferring merge inside the function, where it can be tested:
  the merge is what makes the button appear as soon as a groupmate's
  document syncs, and it was previously spelled out at the call site and
  therefore outside the tested unit.
- Name the type check isCollaborativeDoc, since what it really asks is
  whether the document is multi-writer. Note that the check is
  load-bearing: problem documents also carry a groupId, so dropping it
  would make a groupmate's problem document editable.

Behavior is unchanged. All seven predicate test cases move into
document-utils.test.ts, joined by one covering the document-fallback path
and one asserting the per-field metadata preference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@scytacki

Copy link
Copy Markdown
Member

Pushed a follow-up that relocates and reshapes the predicate. No behavior change — same truth table, same call site semantics.

1. Moved and renamed: src/components/document/sort-work-edit-permission.tscanUserEditDocument in src/models/document/document-utils.ts.

"Can this user edit this document?" is a model-level permission question. Its sibling — "can this user see this document?" — is already isDocumentAccessibleToUser in document-utils.ts, and the two are called two lines apart in sort-work-document-area.tsx. Keeping them in separate layers meant a reader had to know that one permission question lives with the models and its twin lives with the components. The rename drops SortWork because the rule isn't Sort-Work-specific.

2. Takes { document, documentMetadata, user } instead of five loose primitives, mirroring isDocumentAccessibleToUser exactly. The call site collapses to:

const showEdit = canUserEditDocument({
  document: openDocument, documentMetadata: openDocumentMetadata, user
});

This matters beyond tidiness: the metadata-preferring merge is the interesting part of the fix, and it was previously outside the tested unit. The reactivity bug this PR fixes lives in the "prefer metadata, fall back to the document" merge. With the merge spelled out at the call site, the unit test could only assert the truth table over already-merged values. Inside the function it's directly testable — there's now a case passing a documentMetadata carrying the group id alongside a document whose groupId is still undefined, asserting true.

3. Named the axis test: const isCollaborativeDoc = type === GroupDocument;

That expression isn't really asking "is this a group document" for its own sake — it's asking "is this a multi-writer document that people other than its owner may edit." Worth noting for future readers: the type check is load-bearing. Problem documents also carry a groupId (it's how getProblemDocumentsForGroup finds them), so simplifying to !!groupId && groupId === user.currentGroupId would make a groupmate's problem document editable.

Not changed here: the resources-pane edit gate at document-view.tsx:231. Switching it to this predicate is a real behavior change (a group document in the class-work tab would become editable) that needs its own verification. The point of the shape above is that adopting it there later is a one-line change.

Type note: the documentMetadata param is Omit<IDocumentMetadata, "properties">. IDocumentMetadata on its own doesn't accept the IDocumentMetadataModel the call site passes, because the model stores properties in an MST observable map rather than a Record<string, string>Omit<..., "properties"> is the same workaround the compile-time compatibility checks in document-metadata-model.ts already use. IDocumentMetadata rather than IDocumentMetadataBase because only the former carries context_id.

Verification: npm run check:types clean; npm run lint:build 0 errors, no new warnings; npm test -- src/models/document src/components/document → 20 suites / 235 tests pass. All seven original predicate cases were kept, moved into document-utils.test.ts, and joined by one covering the document-fallback path and one asserting the per-field metadata preference.

@scytacki scytacki left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lbondaryk I revised this so it is compatible with the PRs I'm working on. Please test it to make sure it is still working before you merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants