From adeb0ec0a36d62be55460a60175cc8e5185b8f22 Mon Sep 17 00:00:00 2001 From: teleaziz Date: Tue, 14 Jul 2026 14:56:16 +0400 Subject: [PATCH 1/3] Design: Element-anchored review comments with selective agent feedback routing --- .changeset/quiet-comments-route.md | 5 + .../docs/content/toolkit-comments-review.mdx | 18 +- packages/core/package.json | 2 + packages/core/src/client/analytics.spec.ts | 15 + packages/core/src/client/analytics.ts | 9 +- .../src/client/guided-questions.flow.spec.tsx | 14 + packages/core/src/client/guided-questions.tsx | 4 + packages/core/src/client/i18n.tsx | 3 +- packages/core/src/client/index.ts | 4 + .../client/review/ReviewCommentComposer.tsx | 108 ++ .../client/review/ReviewThreadPanel.spec.tsx | 296 +++++ .../src/client/review/ReviewThreadPanel.tsx | 631 +++++++--- packages/core/src/client/review/index.ts | 8 + packages/core/src/client/review/use-review.ts | 36 +- .../client/use-agent-chat-context.spec.tsx | 61 + .../core/src/client/use-agent-chat-context.ts | 5 +- .../review/actions/create-review-comment.ts | 3 +- .../src/review/actions/get-review-feedback.ts | 17 +- .../review/actions/list-review-comments.ts | 114 +- .../review/actions/reply-review-comment.ts | 55 +- .../review/actions/resolve-review-thread.ts | 31 +- .../src/review/actions/review-actions.spec.ts | 578 +++++++++ .../actions/send-review-thread-to-agent.ts | 62 + packages/core/src/review/identity.ts | 11 + packages/core/src/review/index.ts | 8 + packages/core/src/review/store.spec.ts | 40 + packages/core/src/review/store.ts | 323 ++++- packages/core/src/review/types.ts | 4 + .../core/src/server/action-discovery.spec.ts | 1 + packages/core/src/server/action-discovery.ts | 4 + packages/core/src/vite/action-types-plugin.ts | 4 + .../.agents/skills/design-generation/SKILL.md | 3 + .../skills/design-review-feedback/SKILL.md | 42 + .../.generated/bridge/hit-test.generated.ts | 143 +++ templates/design/AGENTS.md | 6 + templates/design/README.md | 1 + templates/design/actions/navigate.ts | 8 +- templates/design/actions/view-screen.test.ts | 131 ++ templates/design/actions/view-screen.ts | 107 +- templates/design/agent-native.app-skill.json | 5 + .../app/components/design/DesignCanvas.tsx | 111 +- .../app/components/design/EditPanel.tsx | 90 +- .../components/design/ReviewCommentsPanel.tsx | 161 +++ .../components/design/ReviewStatusControl.tsx | 96 ++ .../design/bridge/bridge.guard.spec.ts | 83 ++ .../design/bridge/hit-test.bridge.ts | 166 +++ .../design/app/components/layout/Layout.tsx | 1 + .../visual-editor/ReviewCanvasPins.spec.tsx | 306 +++++ .../visual-editor/ReviewCanvasPins.tsx | 1077 +++++++++++++++++ .../app/components/visual-editor/index.ts | 1 + .../visual-editor/review-canvas-state.spec.ts | 63 + .../visual-editor/review-canvas-state.ts | 42 + .../design/app/hooks/use-design-systems.ts | 4 +- .../design/app/hooks/use-navigation-state.ts | 15 +- .../design/app/hooks/use-question-flow.ts | 8 +- templates/design/app/i18n/ar-SA.ts | 57 +- templates/design/app/i18n/de-DE.ts | 58 +- templates/design/app/i18n/en-US.ts | 57 +- templates/design/app/i18n/es-ES.ts | 57 +- templates/design/app/i18n/fr-FR.ts | 58 +- templates/design/app/i18n/hi-IN.ts | 57 +- templates/design/app/i18n/ja-JP.ts | 57 +- templates/design/app/i18n/ko-KR.ts | 57 +- templates/design/app/i18n/pt-BR.ts | 58 +- templates/design/app/i18n/zh-CN.ts | 57 +- templates/design/app/i18n/zh-TW.ts | 51 + .../app/pages/DesignEditor.selection.test.ts | 18 + templates/design/app/pages/DesignEditor.tsx | 451 +++++-- templates/design/app/pages/Present.tsx | 186 ++- .../app/pages/design-editor/tool-state.ts | 10 + .../app/pages/present-review-state.spec.ts | 47 + .../design/app/pages/present-review-state.ts | 18 + templates/design/app/public-routes.spec.ts | 21 + templates/design/app/public-routes.ts | 8 + templates/design/app/root.tsx | 13 +- ...ments-on-shared-designs-and-apply-verif.md | 6 + .../plugins/agent-chat.instructions.spec.ts | 26 + templates/design/server/plugins/agent-chat.ts | 10 + templates/design/server/plugins/auth.spec.ts | 24 +- templates/design/server/plugins/auth.ts | 12 +- .../design/server/plugins/review.spec.ts | 29 + templates/design/server/plugins/review.ts | 9 + templates/design/shared/review-anchor.spec.ts | 40 + templates/design/shared/review-anchor.ts | 83 ++ .../design/shared/review-summary.spec.ts | 22 + templates/design/shared/review-summary.ts | 25 + 86 files changed, 6427 insertions(+), 399 deletions(-) create mode 100644 .changeset/quiet-comments-route.md create mode 100644 packages/core/src/client/review/ReviewCommentComposer.tsx create mode 100644 packages/core/src/client/review/ReviewThreadPanel.spec.tsx create mode 100644 packages/core/src/client/use-agent-chat-context.spec.tsx create mode 100644 packages/core/src/review/actions/review-actions.spec.ts create mode 100644 packages/core/src/review/actions/send-review-thread-to-agent.ts create mode 100644 packages/core/src/review/identity.ts create mode 100644 templates/design/.agents/skills/design-review-feedback/SKILL.md create mode 100644 templates/design/app/components/design/ReviewCommentsPanel.tsx create mode 100644 templates/design/app/components/design/ReviewStatusControl.tsx create mode 100644 templates/design/app/components/visual-editor/ReviewCanvasPins.spec.tsx create mode 100644 templates/design/app/components/visual-editor/ReviewCanvasPins.tsx create mode 100644 templates/design/app/components/visual-editor/review-canvas-state.spec.ts create mode 100644 templates/design/app/components/visual-editor/review-canvas-state.ts create mode 100644 templates/design/app/pages/present-review-state.spec.ts create mode 100644 templates/design/app/pages/present-review-state.ts create mode 100644 templates/design/app/public-routes.spec.ts create mode 100644 templates/design/app/public-routes.ts create mode 100644 templates/design/changelog/2026-07-13-reviewers-can-pin-comments-on-shared-designs-and-apply-verif.md create mode 100644 templates/design/server/plugins/agent-chat.instructions.spec.ts create mode 100644 templates/design/server/plugins/review.spec.ts create mode 100644 templates/design/server/plugins/review.ts create mode 100644 templates/design/shared/review-anchor.spec.ts create mode 100644 templates/design/shared/review-anchor.ts create mode 100644 templates/design/shared/review-summary.spec.ts create mode 100644 templates/design/shared/review-summary.ts diff --git a/.changeset/quiet-comments-route.md b/.changeset/quiet-comments-route.md new file mode 100644 index 0000000000..2f56b34694 --- /dev/null +++ b/.changeset/quiet-comments-route.md @@ -0,0 +1,5 @@ +--- +"@agent-native/core": patch +--- + +Add public-safe review reads and client gating, root-thread agent routing, durable resolution notes, and capability-aware review panels. diff --git a/packages/core/docs/content/toolkit-comments-review.mdx b/packages/core/docs/content/toolkit-comments-review.mdx index 051585344b..94cde93f92 100644 --- a/packages/core/docs/content/toolkit-comments-review.mdx +++ b/packages/core/docs/content/toolkit-comments-review.mdx @@ -24,9 +24,9 @@ status such as draft, review, approved, or needs changes. | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `registerReviewableResource()` | Register a resource type and optional access resolver for review actions. | | Comment actions | `list-review-comments`, `create-review-comment`, `reply-review-comment`, `resolve-review-thread`, and `delete-review-comment`. | -| Feedback actions | `get-review-feedback` and `consume-review-feedback` for agent-readable review queues. | +| Feedback actions | `get-review-feedback`, `send-review-thread-to-agent`, and `consume-review-feedback` for root-thread agent queues and human handoffs. | | Review status action | `set-review-status` for draft, in review, approved, and changes requested states. | -| `ReviewThreadPanel` | Drop-in React panel with comment composer, replies, resolve, and delete controls. | +| `ReviewThreadPanel` | Drop-in React panel with capability-gated composer, reply, resolve, delete, and custom thread-action controls. | | `ReviewStatusBadge` | Controlled status badge for app headers, sidebars, and review panels. | | Review hooks | `useReviewComments()`, `useCreateReviewComment()`, `useReplyReviewComment()`, `useResolveReviewThread()`, `useDeleteReviewComment()`, and `useSetReviewStatus()`. | | Mentions helpers | `extractReviewMentions()` and `normalizeReviewMentions()` for `@[Label](mailto:email@example.com)` tokens. | @@ -42,6 +42,13 @@ specialize anchor shapes, but the common model preserves: - `parentCommentId` for replies - optional `mentions` - optional `anchor` or `resolutionTarget` +- optional `resolutionNote` on a resolved root thread + +`list-review-comments` supports anonymous reads for resources whose access +resolver grants public viewer access. Public viewer responses redact ownership, +organization, email, and actor metadata while preserving safe display names and +the caller-specific `canDelete` capability. Its `summary` reports unpaginated +open-root and agent-queue counts even when the returned comment page is bounded. ## Register A Resource {#register-resource} @@ -76,6 +83,9 @@ export function DocumentReview({ documentId }: { documentId: string }) { resourceType="document" resourceId={documentId} targetId="intro-section" + canReply + canResolve={false} + canDeleteComment={(comment) => comment.canDelete === true} /> ); } @@ -93,6 +103,10 @@ actions and thread UI. - Agents can read open review feedback before changing code or content. - `consumedAt` and `resolved` are separate. An agent can consume a piece of feedback without closing the reviewer-visible thread. +- The root comment owns routing. A human-targeted root is absent from the agent + queue; sending it back to the agent clears its previous consumption marker. +- Agents can pass `resolutionNote` to `resolve-review-thread` so verification + evidence remains attached to the resolved root. ## What's next diff --git a/packages/core/package.json b/packages/core/package.json index 7e26a94282..0336979a52 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -156,6 +156,7 @@ "./review/actions/consume-review-feedback": "./dist/review/actions/consume-review-feedback.js", "./review/actions/get-review-feedback": "./dist/review/actions/get-review-feedback.js", "./review/actions/set-review-status": "./dist/review/actions/set-review-status.js", + "./review/actions/send-review-thread-to-agent": "./dist/review/actions/send-review-thread-to-agent.js", "./comments-review/actions/list-review-comments": "./dist/review/actions/list-review-comments.js", "./comments-review/actions/create-review-comment": "./dist/review/actions/create-review-comment.js", "./comments-review/actions/reply-review-comment": "./dist/review/actions/reply-review-comment.js", @@ -164,6 +165,7 @@ "./comments-review/actions/consume-review-feedback": "./dist/review/actions/consume-review-feedback.js", "./comments-review/actions/get-review-feedback": "./dist/review/actions/get-review-feedback.js", "./comments-review/actions/set-review-status": "./dist/review/actions/set-review-status.js", + "./comments-review/actions/send-review-thread-to-agent": "./dist/review/actions/send-review-thread-to-agent.js", "./sharing": "./dist/sharing/index.js", "./sharing/actions/share-resource": "./dist/sharing/actions/share-resource.js", "./sharing/actions/unshare-resource": "./dist/sharing/actions/unshare-resource.js", diff --git a/packages/core/src/client/analytics.spec.ts b/packages/core/src/client/analytics.spec.ts index c5aa6f141a..c4b716cf63 100644 --- a/packages/core/src/client/analytics.spec.ts +++ b/packages/core/src/client/analytics.spec.ts @@ -211,6 +211,21 @@ describe("browser analytics pageviews", () => { }); }); + it("can skip the authenticated engine-status probe on public routes", async () => { + installBrowser("https://design.agent-native.com/present/public-design"); + const { fetchMock } = installFetch(); + const { configureTracking } = await freshAnalytics(); + + configureTracking({ llmConnectionStatus: false }); + await tick(); + + expect( + fetchMock.mock.calls.some(([url]) => + String(url).includes("/_agent-native/agent-engine/status"), + ), + ).toBe(false); + }); + it("keeps sanitized tracking but disables content capture on local Plan routes", async () => { const { gtag } = installBrowser( "https://plan.agent-native.com/local-plans/local#bridge=secret", diff --git a/packages/core/src/client/analytics.ts b/packages/core/src/client/analytics.ts index 904f7106ac..0f4f96fcce 100644 --- a/packages/core/src/client/analytics.ts +++ b/packages/core/src/client/analytics.ts @@ -96,6 +96,11 @@ export type ConfigureTrackingOptions = { contentCapture?: boolean; /** Resolve content capture synchronously for each browser pathname. */ contentCaptureForPath?: (pathname: string) => boolean; + /** + * Whether tracking may read the authenticated agent-engine status endpoint. + * Disable this on anonymous/public routes to avoid an expected 401 request. + */ + llmConnectionStatus?: boolean; sessionReplay?: boolean | SessionReplayOptions; /** * First-party, Sentry-style error capture. Auto-captures uncaught errors @@ -1029,7 +1034,9 @@ export function configureTracking(options: ConfigureTrackingOptions): void { ensureSentry(); ensureAmplitude(); captureFirstTouchAttribution(); - installLlmConnectionRefresh(); + if (options.llmConnectionStatus !== false) { + installLlmConnectionRefresh(); + } installTrackingAuthSessionRefresh(); installPageviewTracking(); maybeInstallSessionReplay( diff --git a/packages/core/src/client/guided-questions.flow.spec.tsx b/packages/core/src/client/guided-questions.flow.spec.tsx index ac0b253ad8..9cdf070910 100644 --- a/packages/core/src/client/guided-questions.flow.spec.tsx +++ b/packages/core/src/client/guided-questions.flow.spec.tsx @@ -161,6 +161,20 @@ describe("useGuidedQuestionFlow scoped reads", () => { expect(requestedKeys).not.toContain("guided-questions:undefined"); }); + it("does not read application state when disabled", async () => { + const fetchMock = vi.fn(); + vi.stubGlobal("fetch", fetchMock); + + const result = await renderFlow({ + enabled: false, + stateKey: "guided-questions", + queryKey: ["guided-questions"], + }); + + expect(result.current().questions).toBeNull(); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it("refetches on a key-specific DB-sync wakeup without fixed polling", async () => { let hasQuestion = false; const fetchMock = vi.fn( diff --git a/packages/core/src/client/guided-questions.tsx b/packages/core/src/client/guided-questions.tsx index fe278bbce2..f71af8f53f 100644 --- a/packages/core/src/client/guided-questions.tsx +++ b/packages/core/src/client/guided-questions.tsx @@ -959,6 +959,8 @@ function normalizeBrowserTabId(browserTabId?: string): string | undefined { } export interface UseGuidedQuestionFlowOptions { + /** Disable application-state reads for signed-out or otherwise inactive surfaces. */ + enabled?: boolean; stateKey?: string; /** * The current browser tab id. Agent actions that write the guided-questions @@ -980,6 +982,7 @@ export interface UseGuidedQuestionFlowOptions { } export function useGuidedQuestionFlow({ + enabled = true, stateKey = "show-questions", browserTabId, queryKey = ["show-questions"], @@ -1030,6 +1033,7 @@ export function useGuidedQuestionFlow({ const { data } = useQuery({ queryKey: resolvedQueryKey, + enabled, queryFn: async () => { const read = async (key: string) => { const res = await fetch(endpointFor(key)); diff --git a/packages/core/src/client/i18n.tsx b/packages/core/src/client/i18n.tsx index 1c7308ebfa..c173ede2b9 100644 --- a/packages/core/src/client/i18n.tsx +++ b/packages/core/src/client/i18n.tsx @@ -413,6 +413,7 @@ export function AgentNativeI18nProvider({ }, []); useEffect(() => { + if (!persistPreference) return; void setClientAppState( "localization", { @@ -424,7 +425,7 @@ export function AgentNativeI18nProvider({ ).catch(() => { // Public/anonymous pages cannot write app-state; localization still works. }); - }, [locale, preference]); + }, [locale, persistPreference, preference]); const setPreference = useCallback( async (next: LocalePreference) => { diff --git a/packages/core/src/client/index.ts b/packages/core/src/client/index.ts index c799b04fb0..74ed1dec19 100644 --- a/packages/core/src/client/index.ts +++ b/packages/core/src/client/index.ts @@ -851,6 +851,7 @@ export { type VersionHistoryPanelProps, } from "./history/index.js"; export { + ReviewCommentComposer, ReviewStatusBadge, ReviewThreadPanel, buildReviewThreads, @@ -861,6 +862,7 @@ export { useResolveReviewThread, useReviewComments, useReviewFeedback, + useSendReviewThreadToAgent, useSetReviewStatus, type ConsumeReviewFeedbackInput, type CreateReviewCommentInput, @@ -872,9 +874,11 @@ export { type ReplyReviewCommentInput, type ResolveReviewThreadInput, type ReviewStatusBadgeProps, + type ReviewCommentComposerProps, type ReviewThread, type ReviewThreadPanelProps, type SetReviewStatusInput, + type SendReviewThreadToAgentInput, } from "./review/index.js"; export type { AppToFrameMessage, diff --git a/packages/core/src/client/review/ReviewCommentComposer.tsx b/packages/core/src/client/review/ReviewCommentComposer.tsx new file mode 100644 index 0000000000..e4277cb09c --- /dev/null +++ b/packages/core/src/client/review/ReviewCommentComposer.tsx @@ -0,0 +1,108 @@ +import { Button } from "@agent-native/toolkit/ui/button"; +import { Spinner } from "@agent-native/toolkit/ui/spinner"; +import { Textarea } from "@agent-native/toolkit/ui/textarea"; +import { IconMessageCircle, IconSend } from "@tabler/icons-react"; + +import type { ReviewResolutionTarget } from "../../review/types.js"; +import { cn } from "../utils.js"; + +export interface ReviewCommentComposerProps { + value: string; + onChange: (value: string) => void; + onSubmit: (resolutionTarget: ReviewResolutionTarget) => void; + submittingTarget?: ReviewResolutionTarget | null; + disabled?: boolean; + showAgentAction?: boolean; + placeholder?: string; + commentLabel?: string; + agentLabel?: string; + autoFocus?: boolean; + submitOnEnter?: boolean; + onEscape?: () => void; + className?: string; +} + +export function ReviewCommentComposer({ + value, + onChange, + onSubmit, + submittingTarget = null, + disabled = false, + showAgentAction = false, + placeholder = "Add a comment...", + commentLabel = "Comment", + agentLabel = "Send to agent", + autoFocus = false, + submitOnEnter = false, + onEscape, + className, +}: ReviewCommentComposerProps) { + const canSubmit = Boolean(value.trim()) && !disabled; + const submit = (resolutionTarget: ReviewResolutionTarget) => { + if (!canSubmit) return; + onSubmit(resolutionTarget); + }; + + return ( +
{ + event.preventDefault(); + submit("human"); + }} + > +