Skip to content

Team collaboration (2/3): web — in-project collaboration + i18n - #5283

Merged
lefarcen merged 1 commit into
feat/workspace-teamfrom
feat/collab-web-project
Jul 9, 2026
Merged

Team collaboration (2/3): web — in-project collaboration + i18n#5283
lefarcen merged 1 commit into
feat/workspace-teamfrom
feat/collab-web-project

Conversation

@lefarcen

@lefarcen lefarcen commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Why

The client layer for in-project collaboration, on top of the backend contracts
(part 1 of the stack). Kept separate so the collab client primitives review apart
from the larger surface work.

What users will see

Nothing on its own — this is the collaboration client layer the project view and
team surfaces consume: the presence overlay, the collab client/session, the
per-project collab hook (presence + read-only), the workspace-context hook, and
the invite-continuation state machine.

Surface area

  • API / contract — consumes the shared collab/workspace contracts
  • UI (no standalone screen) · [ ] i18n · [ ] New dependency · [ ] Default change

Validation

  • pnpm --filter @open-design/web typecheck
  • collab client / session / presence-bar suites green

@lefarcen
lefarcen requested a review from mrcfps July 8, 2026 03:50
@lefarcen lefarcen added size/XL PR changes 700-1500 lines risk/medium Medium risk: regular code changes type/feature New feature labels Jul 8, 2026
@lefarcen

lefarcen commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Could you expand the PR body a bit before pool review picks this up? For an XL UI change, it's still missing the Why / What users will see / Screenshots / Validation pieces, and those will make the collab + read-only flows much faster to scope.

Related: part of the same team-collab series as #5277, #5280, #5281, #5282, and #5284.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Visual regression review

Head: 030ada6 · Base: 5caca3c

Baseline unavailable; PR screenshots are new visual cases and need baseline review.

0 changed · 0 unchanged · 53 new without baseline · 0 failed

New cases without baselines

PR PR PR
visual-avatar-local-agent-list
pr
visual-avatar-local-agent-list-panel
pr
visual-avatar-menu
pr
visual-avatar-menu-panel
pr
visual-avatar-open-design-account
pr
visual-critical-settings
pr
visual-critical-workspace
pr
visual-critical-workspace-preview
pr
visual-design-system-detail
pr
visual-design-systems
pr
visual-home
pr
visual-home-catalog
pr
visual-home-context-picker
pr
visual-home-context-picker-popover
pr
visual-home-plugin-filter
pr
visual-home-plugin-use-staged
pr
visual-home-plugin-use-with-query
pr
visual-home-staged-attachment
pr
visual-integrations
pr
visual-integrations-mcp
pr

33 additional new case(s) omitted from this comment.

Visual diff is advisory only and does not block merging.

@mrcfps mrcfps 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.

@lefarcen thanks for pushing this collaboration slice forward. I reviewed the changed web client paths for the presence/read-only/comment-anchor flow, and I found two read-only enforcement gaps that let team viewers still mutate shared project files.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread apps/web/src/components/ProjectView.tsx Outdated
) : null}
<FileWorkspace
projectId={project.id}
viewerOnly={projectCollab.viewerOnly}

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.

This wires viewerOnly into the workspace shell, but it does not actually protect the open file viewer. FileWorkspace still renders FileViewer without a read-only prop, and FileViewer owns several write paths for already-open files: manual edit save/undo/redo (applyManualEdit, undoManualEdit, redoManualEdit), markdown autosave (saveMarkdownText), markdown image upload (insertImageFiles), and version restore (handleVersionRestored). That means a non-owner can open an existing HTML/Markdown file in a shared project and still persist changes even though the new notice says they cannot edit files. Please propagate the read-only state into FileViewer and gate all file-mutating actions there, while keeping comment-only actions available.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

}

const fileActions = (
const fileActions = viewerOnly ? null : (

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.

Hiding the top create/upload buttons leaves other Design Files write paths active for viewerOnly users. The same component still unconditionally handles drops via onDrop={handleDrop} and calls onUploadFiles(dropped), and the batch bar still renders its delete button whenever selected.size > 0, so a read-only viewer can upload by dragging files into the panel and can delete files after selecting rows. Since this PR defines viewerOnly as withholding create/upload/delete affordances, please make the panel read-only end-to-end: disable/omit drag handlers and copy drop effects when viewerOnly, suppress or disable batch deletion, and ideally hide row mutation menus as well.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen

lefarcen commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@lefarcen mrcfps's current blockers are the two read-only enforcement gaps in FileViewer and DesignFilesPanel, so I'd treat those comments as the merge gate for this head.

💡 To drive this PR to merge hands-free, paste this to your AI coding agent (Claude Code / Codex / opencode / Cursor …):
Take over nexu-io/open-design#5283 until it merges — read https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/SKILL.md and follow it.

@lefarcen lefarcen changed the title Team collaboration (5/6): web — in-project collaboration Team collaboration (2/3): web — in-project collaboration + i18n Jul 8, 2026
@lefarcen
lefarcen changed the base branch from feat/collab-web-i18n to feat/collab-daemon-resources July 8, 2026 06:41
@lefarcen
lefarcen force-pushed the feat/collab-daemon-resources branch from a8cd808 to 6def8f5 Compare July 9, 2026 03:00
@lefarcen
lefarcen force-pushed the feat/collab-web-project branch from c5aa916 to fd92a7d Compare July 9, 2026 03:00
@lefarcen
lefarcen requested a review from mrcfps July 9, 2026 03:07

@mrcfps mrcfps 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.

@lefarcen thanks for continuing to break the collaboration work into reviewable layers. I reviewed the current web collab client/session/hooks/test changes and found one small source hygiene issue that is worth fixing before this layer becomes a dependency for the next UI slice.

Non-blocking finding

apps/web/src/collab/useCollab.ts, memberKey in useCollab: the new source file contains literal NUL bytes between the template segments on the memberKey line. Evidence from the live PR diff: GitHub/gh pr diff --patch renders this TypeScript file as a GIT binary patch, and locally file apps/web/src/collab/useCollab.ts reports data; grep -nUa locates the NULs on the memberKey line. That makes the file hard to review in GitHub, hides future line-level diffs, and can cause text-oriented repo tooling or search to skip a normal TS source file. Please replace the raw separators with text source that produces the same runtime value, for example \u0000 escapes inside the template literal or a printable/structured key such as JSON.stringify([member.memberId, member.name ?? '', member.role ?? '']).

I could not run the focused web validation locally because this prepared worktree has no node_modules; pnpm --filter @open-design/web typecheck and the focused Vitest command failed before reaching PR code due missing packages like next, react, and vitest.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen

lefarcen commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@lefarcen The current head looks down to the one non-blocking cleanup @mrcfps called out in apps/web/src/collab/useCollab.ts: replacing the literal NUL separators on memberKey with normal text source so the file diffs and tools stay readable.

Once that hygiene fix lands, this layer should be in a much cleaner spot for the next pass.

@lefarcen
lefarcen force-pushed the feat/collab-web-project branch from fd92a7d to d327f8a Compare July 9, 2026 03:41
@lefarcen
lefarcen requested a review from mrcfps July 9, 2026 03:42

@mrcfps mrcfps 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.

@lefarcen thanks for the continued iteration here. I reviewed the current web collaboration client/session/hooks/test slice against the live head and the earlier feedback; the previous read-only blockers are no longer in this scoped diff, but one source hygiene issue is still present on this head.

Non-blocking finding

apps/web/src/collab/useCollab.ts, memberKey in useCollab: the new TypeScript source still contains literal NUL bytes between the template segments on the memberKey line. This is visible from the live PR diff because GitHub/gh pr diff --patch renders apps/web/src/collab/useCollab.ts as a GIT binary patch, and locally file apps/web/src/collab/useCollab.ts reports data; grep -nUa locates the NUL bytes on the memberKey line. That makes the file hard to review in GitHub, hides future line-level diffs, and can cause text-oriented repo tooling/search to skip what should be normal TS source. Please replace the raw separators with text source that preserves the same runtime identity key, for example escaped \u0000 separators in the template literal or a printable/structured key such as JSON.stringify([member.memberId, member.name ?? '', member.role ?? '']).

I could not complete local validation in this prepared worktree because node_modules is missing: pnpm --filter @open-design/web typecheck failed before reaching PR code on missing Next/React/Vitest packages, and the focused Vitest command could not find vitest.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen
lefarcen force-pushed the feat/collab-daemon-resources branch from e839441 to d9b17be Compare July 9, 2026 04:36
@lefarcen
lefarcen force-pushed the feat/collab-web-project branch from d327f8a to 245bde8 Compare July 9, 2026 04:36

@mrcfps mrcfps 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.

@lefarcen thanks for continuing to iterate on this collaboration client layer. I reviewed the current head's changed web collab files, checked the daemon route/contract shapes against the new client calls, and attempted the focused collab Vitest run locally. The prior read-only blockers are not in this scoped diff, but the source hygiene issue in the current head is still present and worth cleaning up before this layer becomes a dependency for the next UI slice.

Non-blocking finding

apps/web/src/collab/useCollab.ts, memberKey in useCollab: the new TypeScript source still contains literal NUL bytes between the template segments on the memberKey line. Evidence from the live PR diff/current checkout: gh pr diff --patch renders apps/web/src/collab/useCollab.ts as a GIT binary patch, file apps/web/src/collab/useCollab.ts reports data, and grep -nUa locates the NUL bytes on the memberKey line. That makes this normal TS source hard to review in GitHub, hides future line-level diffs, and can cause text-oriented repo tooling/search to skip it. Please replace the raw separators with text source that preserves the same runtime identity key, for example escaped separators in the template literal or a printable structured key such as JSON.stringify([member.memberId, member.name ?? '', member.role ?? '']).

I could not complete the focused local validation in this prepared worktree because node_modules is missing; pnpm --filter @open-design/web exec vitest run apps/web/tests/collab-client.test.ts apps/web/tests/collab-session.test.ts apps/web/tests/collab-presence-bar.test.tsx failed before reaching PR code with Command "vitest" not found.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen
lefarcen force-pushed the feat/collab-daemon-resources branch from d9b17be to 5caca3c Compare July 9, 2026 05:24
@lefarcen
lefarcen force-pushed the feat/collab-web-project branch from 245bde8 to 030ada6 Compare July 9, 2026 05:24
@lefarcen

lefarcen commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the source-hygiene finding: useCollab's memberKey used raw NUL bytes as template separators, which made git treat the file as binary. It now builds a printable structured key with JSON.stringify([memberId, name, role]), preserving the same runtime identity. file now reports the source as text and the diff renders line-by-line again. The collab client/session/hooks suites stay green.

@lefarcen
lefarcen requested a review from mrcfps July 9, 2026 05:27

@mrcfps mrcfps 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.

@lefarcen thanks for continuing to tighten this collaboration client layer. I reviewed the current head against the resolved read-only/source-hygiene feedback and found one small lifecycle issue that is worth addressing, but it should not block the PR.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

const version = typeof body?.publishedVersion === 'number' ? body.publishedVersion : null;
const syncState = (body?.syncState as ProjectSyncState | undefined) ?? null;
const ownerMemberId = typeof body?.ownerMemberId === 'string' ? body.ownerMemberId : null;
this.update({ publishedVersion: version, syncState, ownerMemberId });

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.

pollStatus() and heartbeat() both await network calls and then call this.update(...) without checking whether the client is still running. stop() clears future intervals, but it does not cancel the already-started immediate heartbeat/status requests from start(), and useCollab calls stop() during project/member changes before creating the next client. A slow response from the old project can therefore still invoke the old client's onUpdate and overwrite the hook snapshot after cleanup, briefly showing stale presence/read-only state for the previous project. Please either guard after each await with if (!this.running) return, or give each client an AbortController/generation token so stopped clients cannot publish late snapshots.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen
lefarcen changed the base branch from feat/collab-daemon-resources to feat/workspace-team July 9, 2026 06:12
@lefarcen
lefarcen merged commit f2c3f2a into feat/workspace-team Jul 9, 2026
42 of 44 checks passed
@Siri-Ray Siri-Ray mentioned this pull request Jul 9, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/medium Medium risk: regular code changes size/XL PR changes 700-1500 lines type/feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants