Skip to content

feat(notes): domain access share-modal tab (issue #663)#792

Merged
otomatty merged 3 commits into
developfrom
claude/fix-issue-663-oUEc0
Apr 28, 2026
Merged

feat(notes): domain access share-modal tab (issue #663)#792
otomatty merged 3 commits into
developfrom
claude/fix-issue-663-oUEc0

Conversation

@otomatty
Copy link
Copy Markdown
Owner

@otomatty otomatty commented Apr 27, 2026

Wires the existing note_domain_access backend (POST/GET/DELETE
/notes/:id/domain-access) into the share modal as a new "domains" tab.
Adds the React Query hook, API client methods, client-side free-email
validation mirror, and Japanese / English i18n strings. Adding an
editor rule prompts a confirmation since it grants edit access to
the entire domain; verifiedAt stays null in v1, so each row carries
an "unverified" badge until DNS-TXT verification ships.


Open in Devin Review

Summary by CodeRabbit

Release Notes

  • New Features

    • Added domain-based access management to the note share modal, allowing owners to grant viewer or editor access to specific domains
    • Includes input validation that prevents free webmail domains and invalid format entries
    • Displays verification status badges for domain rules
  • Localization

    • Added comprehensive English and Japanese translations for the new domain access interface

Wires the existing `note_domain_access` backend (POST/GET/DELETE
/notes/:id/domain-access) into the share modal as a new "domains" tab.
Adds the React Query hook, API client methods, client-side free-email
validation mirror, and Japanese / English i18n strings. Adding an
`editor` rule prompts a confirmation since it grants edit access to
the entire domain; `verifiedAt` stays null in v1, so each row carries
an "unverified" badge until DNS-TXT verification ships.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b18dfabd-cd59-4011-ab8b-473a3b3a5543

📥 Commits

Reviewing files that changed from the base of the PR and between a63e8c2 and e4f27db.

📒 Files selected for processing (7)
  • packages/shared/package.json
  • packages/shared/src/freeEmailDomains.test.ts
  • packages/shared/src/freeEmailDomains.ts
  • packages/shared/src/index.ts
  • server/api/src/lib/freeEmailDomains.ts
  • src/lib/domainValidation.ts
  • src/lib/freeEmailDomainsSync.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/shared/package.json
  • server/api/src/lib/freeEmailDomains.ts
  • packages/shared/src/freeEmailDomains.test.ts

📝 Walkthrough

Walkthrough

Adds domain-access rule management for notes via a new React Query hook set, API endpoints, UI component within the share modal, client-side domain validation utilities, internationalization strings, and synchronization tests to maintain consistency between server and client domain lists.

Changes

Cohort / File(s) Summary
Domain Access Hooks
src/hooks/useDomainAccess.ts
New React Query hooks for managing domain-access rules: useDomainAccessForNote fetches rules, useCreateDomainAccess adds new rules with cache invalidation, useDeleteDomainAccess removes rules with cache invalidation.
Share Modal Component & Tests
src/pages/NoteView/ShareModal/ShareModalDomainTab.tsx, src/pages/NoteView/ShareModal/ShareModalDomainTab.test.tsx, src/pages/NoteView/ShareModal/NoteShareModal.tsx, src/pages/NoteView/ShareModal/NoteShareModal.test.tsx
New ShareModalDomainTab component for domain-rule UI with validation, creation, and deletion flows; updated NoteShareModal to enable domains tab by default and conditionally render the domain tab with focus fallback logic.
API Layer
src/lib/api/types.ts, src/lib/api/apiClient.ts
New TypeScript types (DomainAccessRow, CreateDomainAccessBody) and API client methods for listing, creating, and deleting domain-access rules via typed REST endpoints.
Domain Validation (Shared)
packages/shared/src/freeEmailDomains.ts, packages/shared/src/freeEmailDomains.test.ts, packages/shared/src/index.ts, packages/shared/package.json, src/lib/domainValidation.ts
New canonical domain validation module in shared package: exports free-email deny-list, domain regex, and normalizeDomainInput function; client-side wrapper re-exports canonical artifacts; new subpath export in package.json.
Domain Sync & Documentation
src/lib/freeEmailDomainsSync.test.ts, server/api/src/lib/freeEmailDomains.ts
Drift-detection test ensuring server and client domain constants remain synchronized; server file JSDoc clarifies canonical source and sync requirements.
Internationalization
src/i18n/locales/en/notes.json, src/i18n/locales/ja/notes.json
New domain-access UI strings for English and Japanese locales including tab labels, form controls, role descriptions, validation errors, and state messaging.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Component as ShareModalDomainTab
    participant Hooks as React Query Hooks
    participant API as API Client
    participant Server as Server

    User->>Component: Opens domains tab (enabled, noteId)
    Component->>Hooks: Call useDomainAccessForNote(noteId)
    Hooks->>API: GET /api/notes/{noteId}/domain-access
    API->>Server: Query request
    Server-->>API: Return domain-access rules
    API-->>Hooks: Return rules data
    Hooks-->>Component: Render rules with status

    User->>Component: Add new domain (role: viewer)
    Component->>Component: Validate domain (normalizeDomainInput)
    Component->>Hooks: Call useCreateDomainAccess().mutateAsync()
    Hooks->>API: POST /api/notes/{noteId}/domain-access
    API->>Server: Create request
    Server-->>API: Return created rule
    API-->>Hooks: Success → Invalidate list query
    Hooks->>API: GET /api/notes/{noteId}/domain-access (refetch)
    API->>Server: Refetch request
    Server-->>API: Return updated rules
    API-->>Component: Update UI with new rule
    Component->>User: Show success toast

    User->>Component: Delete domain rule
    Component->>Hooks: Call useDeleteDomainAccess().mutateAsync(accessId)
    Hooks->>API: DELETE /api/notes/{noteId}/domain-access/{accessId}
    API->>Server: Delete request
    Server-->>API: Confirm removal
    API-->>Hooks: Success → Invalidate list query
    Hooks->>API: GET /api/notes/{noteId}/domain-access (refetch)
    API-->>Component: Update UI with removed rule
    Component->>User: Show success toast
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 Hops with glee through domain rules new,
A sharing feature, tried and true!
From validation to UI's grace,
Domain access finds its place! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary feature: adding a domain access tab to the share modal. It is concise, clear, and immediately conveys the main purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-issue-663-oUEc0

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements domain-based access control for notes, allowing owners to grant access to users within specific email domains. It adds API client methods, React Query hooks, client-side domain validation, and a new tab in the share modal. Feedback suggests improving error handling by using machine-readable codes instead of string parsing and handling API fetch errors in the UI to avoid misleading 'no rules' messages.

Comment on lines +230 to +237
const lower = error.message.toLowerCase();
if (lower.includes("free email")) {
message = t("notes.domainTabCreateFailedFreeEmail", { domain });
} else if (lower.includes("invalid format")) {
message = t("notes.domainTabCreateFailedInvalid");
} else if (lower.includes("required")) {
message = t("notes.domainTabCreateFailedEmpty");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The error handling logic here relies on parsing human-readable error messages (e.g., lower.includes("free email")), which is fragile. If the backend changes its error strings, this logic will break. It is recommended to use machine-readable error codes (e.g., error.code) to determine the type of error and display the appropriate message.

Comment on lines +292 to +296
{isLoading ? (
<p className="text-muted-foreground text-sm">{t("notes.domainTabLoading")}</p>
) : !rules || rules.length === 0 ? (
<p className="text-muted-foreground text-sm">{t("notes.domainTabNoRules")}</p>
) : (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The component does not handle the error state of the useDomainAccessForNote query. If the API request fails, isLoading will become false and rules will be undefined, which results in the UI showing the "No domain rules yet" message. This is misleading for the user. You should check the isError state from the hook and display an appropriate error message if the fetch fails.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5efd7ca0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +294 to +295
) : !rules || rules.length === 0 ? (
<p className="text-muted-foreground text-sm">{t("notes.domainTabNoRules")}</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish load failures from empty domain-rule lists

When useDomainAccessForNote fails (network/500/403), rules stays undefined and this branch renders the empty-state text, so users are told "No domain rules yet" even though loading actually failed. In that scenario owners can make decisions based on a false view of access policy (e.g., adding broad rules while existing ones are just hidden by an error), so the tab should handle isError/error separately instead of treating undefined as an empty list.

Useful? React with 👍 / 👎.

coderabbitai[bot]

This comment was marked as resolved.

- Drop fragile substring parsing of server error messages in
  `submitCreate`; surface the server's `ApiError.message` verbatim as
  the toast description instead.
- Distinguish load failures from empty rule lists by reading `isError`
  from `useDomainAccessForNote` and rendering a dedicated message so
  owners aren't told "no rules" when the request actually failed.
- Reset `activeTab` to "members" if `showDomainsTab` flips to false
  while the domains tab is active, preventing the Tabs control from
  holding a non-existent value and rendering an empty panel.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/pages/NoteView/ShareModal/ShareModalDomainTab.tsx (1)

192-195: Add TSDoc to the exported ShareModalDomainTab function.

As per coding guidelines, exported functions should have TSDoc/JSDoc comments. The component has a bilingual header comment at the file level, but the exported function itself lacks direct documentation.

Suggested addition
+/**
+ * 共有モーダルのドメインタブ。ドメインルールの追加・一覧・削除を扱う。
+ * Domain tab — handles add / list / remove for domain-access rules.
+ *
+ * `@param` props - Component props including noteId and enabled flag.
+ */
 export function ShareModalDomainTab({ noteId, enabled }: ShareModalDomainTabProps) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/NoteView/ShareModal/ShareModalDomainTab.tsx` around lines 192 -
195, Add a TSDoc block immediately above the exported function declaration for
ShareModalDomainTab describing its purpose (handles add/list/remove of
domain-access rules), annotate the function parameters (document the props
parameter and any important fields it uses) with `@param`, and annotate the return
type with `@returns` (e.g., JSX.Element or React.ReactElement); ensure the comment
follows the project's style (bilingual if preferred) and sits directly above the
ShareModalDomainTab function declaration so tooling and docs pick it up.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/pages/NoteView/ShareModal/ShareModalDomainTab.tsx`:
- Around line 192-195: Add a TSDoc block immediately above the exported function
declaration for ShareModalDomainTab describing its purpose (handles
add/list/remove of domain-access rules), annotate the function parameters
(document the props parameter and any important fields it uses) with `@param`, and
annotate the return type with `@returns` (e.g., JSX.Element or
React.ReactElement); ensure the comment follows the project's style (bilingual
if preferred) and sits directly above the ShareModalDomainTab function
declaration so tooling and docs pick it up.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ee57d510-1188-4458-bf7a-5597064d8485

📥 Commits

Reviewing files that changed from the base of the PR and between b5efd7c and a63e8c2.

📒 Files selected for processing (6)
  • src/i18n/locales/en/notes.json
  • src/i18n/locales/ja/notes.json
  • src/pages/NoteView/ShareModal/NoteShareModal.test.tsx
  • src/pages/NoteView/ShareModal/NoteShareModal.tsx
  • src/pages/NoteView/ShareModal/ShareModalDomainTab.test.tsx
  • src/pages/NoteView/ShareModal/ShareModalDomainTab.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/i18n/locales/ja/notes.json
  • src/i18n/locales/en/notes.json

devin-ai-integration[bot]

This comment was marked as resolved.

…test

Per AGENTS.md "Sharing constants between server and client" the canonical
copy of values duplicated between client and server must live in
`packages/shared` with a CI drift detector that reads the server source
via `fs.readFileSync` and asserts equality (existing pattern:
`src/lib/tagCharacterClassSync.test.ts`).

- Move `FREE_EMAIL_DOMAINS`, `DOMAIN_REGEX`, `normalizeDomainInput`,
  and validation types to `packages/shared/src/freeEmailDomains.ts`.
- Convert `src/lib/domainValidation.ts` to a thin re-export from
  `@zedi/shared/freeEmailDomains` so existing imports keep working.
- Move the unit tests for `normalizeDomainInput` to
  `packages/shared/src/freeEmailDomains.test.ts` (alongside source of truth).
- Add `src/lib/freeEmailDomainsSync.test.ts` to detect drift between
  `@zedi/shared` and the server-side duplicate
  (`server/api/src/lib/freeEmailDomains.ts`) for both the deny-list and
  the `DOMAIN_REGEX` pattern.
- Document the sync obligation in the server file's header.
@otomatty otomatty self-assigned this Apr 28, 2026
@otomatty otomatty merged commit 6ec5d24 into develop Apr 28, 2026
17 checks passed
@otomatty otomatty deleted the claude/fix-issue-663-oUEc0 branch April 28, 2026 01:25
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.

2 participants