Skip to content

Commit a9f4267

Browse files
authored
Merge pull request #3071 from trycompai/main
[comp] Production Deploy
2 parents 9bb40e7 + 1dc92ea commit a9f4267

3 files changed

Lines changed: 136 additions & 6 deletions

File tree

‎apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/PolicyDetails.tsx‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import type { PolicyChatUIMessage } from '../types';
5757
import { PolicyAiAssistant } from './ai/policy-ai-assistant';
5858
import { useSuggestions } from '../hooks/use-suggestions';
5959
import { buildPositionMap } from '../lib/build-position-map';
60+
import { resolveInitialPolicyContent } from '../lib/resolve-initial-content';
6061
import { InlineEditBubble } from './ai/inline-edit-bubble';
6162
import { markdownToTipTapJSON } from './ai/markdown-utils';
6263

@@ -197,12 +198,13 @@ export function PolicyContentManager({
197198
const [editorKey, setEditorKey] = useState(0);
198199
const [activeTab, setActiveTab] = useState<string>(displayFormat);
199200
const previousTabRef = useRef<string>(displayFormat);
200-
const [currentContent, setCurrentContent] = useState<Array<JSONContent>>(() => {
201-
const formattedContent = Array.isArray(policyContent)
202-
? policyContent
203-
: [policyContent as JSONContent];
204-
return formattedContent;
205-
});
201+
const [currentContent, setCurrentContent] = useState<Array<JSONContent>>(() =>
202+
// When opening directly on a specific version (e.g. "Edit" a draft from the
203+
// Versions tab, which remounts this tab fresh with a versionId in the URL),
204+
// seed from that version's content — not the published policyContent — so a
205+
// draft's saved edits don't appear to vanish. See resolve-initial-content.ts.
206+
resolveInitialPolicyContent({ initialVersionId, versions, policyContent }),
207+
);
206208

207209
const [dismissedProposalKey, setDismissedProposalKey] = useState<string | null>(null);
208210
const [editorInstance, setEditorInstance] = useState<TipTapEditor | null>(null);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { JSONContent } from '@tiptap/react';
2+
import { describe, expect, it } from 'vitest';
3+
import { resolveInitialPolicyContent } from '../resolve-initial-content';
4+
5+
const publishedContent: JSONContent[] = [
6+
{ type: 'paragraph', content: [{ type: 'text', text: 'published' }] },
7+
];
8+
const draftContent: JSONContent[] = [
9+
{ type: 'paragraph', content: [{ type: 'text', text: 'draft edit' }] },
10+
];
11+
12+
describe('resolveInitialPolicyContent', () => {
13+
it('seeds from the targeted version when initialVersionId matches a draft (the bug fix)', () => {
14+
const result = resolveInitialPolicyContent({
15+
initialVersionId: 'pv_draft',
16+
versions: [
17+
{ id: 'pv_published', content: publishedContent },
18+
{ id: 'pv_draft', content: draftContent },
19+
],
20+
policyContent: publishedContent,
21+
});
22+
23+
expect(result).toEqual(draftContent);
24+
});
25+
26+
it('falls back to policyContent when no initialVersionId is given', () => {
27+
const result = resolveInitialPolicyContent({
28+
initialVersionId: undefined,
29+
versions: [{ id: 'pv_draft', content: draftContent }],
30+
policyContent: publishedContent,
31+
});
32+
33+
expect(result).toEqual(publishedContent);
34+
});
35+
36+
it('falls back to policyContent when initialVersionId matches no known version', () => {
37+
const result = resolveInitialPolicyContent({
38+
initialVersionId: 'pv_missing',
39+
versions: [{ id: 'pv_draft', content: draftContent }],
40+
policyContent: publishedContent,
41+
});
42+
43+
expect(result).toEqual(publishedContent);
44+
});
45+
46+
it('wraps a single (non-array) version content into an array', () => {
47+
const singleNode: JSONContent = { type: 'paragraph', content: [{ type: 'text', text: 'one' }] };
48+
const result = resolveInitialPolicyContent({
49+
initialVersionId: 'pv_draft',
50+
versions: [{ id: 'pv_draft', content: singleNode }],
51+
policyContent: publishedContent,
52+
});
53+
54+
expect(result).toEqual([singleNode]);
55+
});
56+
57+
it('wraps a single (non-array) policyContent fallback into an array', () => {
58+
const singleNode: JSONContent = { type: 'paragraph', content: [{ type: 'text', text: 'pub' }] };
59+
const result = resolveInitialPolicyContent({
60+
initialVersionId: undefined,
61+
versions: [],
62+
policyContent: singleNode,
63+
});
64+
65+
expect(result).toEqual([singleNode]);
66+
});
67+
68+
it('returns an empty array for an empty draft version (does not fall back)', () => {
69+
const result = resolveInitialPolicyContent({
70+
initialVersionId: 'pv_draft',
71+
versions: [{ id: 'pv_draft', content: [] }],
72+
policyContent: publishedContent,
73+
});
74+
75+
expect(result).toEqual([]);
76+
});
77+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { JSONContent } from '@tiptap/react';
2+
3+
/** Minimal shape needed to resolve a version's editor content. */
4+
interface VersionContentSource {
5+
id: string;
6+
content: unknown;
7+
}
8+
9+
interface ResolveInitialPolicyContentArgs {
10+
/**
11+
* The version the editor is opening on — e.g. the `versionId` in the URL when
12+
* a draft is opened via "Edit" from the Versions tab. Undefined when simply
13+
* viewing the current/published content.
14+
*/
15+
initialVersionId?: string;
16+
/** All known policy versions (each carrying its own content snapshot). */
17+
versions: VersionContentSource[];
18+
/** Fallback content: the published/current version's content. */
19+
policyContent: JSONContent | JSONContent[];
20+
}
21+
22+
/**
23+
* Decide which content the policy editor should display on mount.
24+
*
25+
* The Content tab is unmounted when you switch tabs (the design-system Tabs do
26+
* not keep inactive panels mounted), so clicking "Edit" on a draft from the
27+
* Versions tab remounts it fresh with a `versionId` already in the URL. In that
28+
* case the editor must seed from THAT version's content. Falling back to
29+
* `policyContent` (which is always the published/current version's content) is
30+
* what made a draft's saved edits appear to vanish.
31+
*
32+
* Pure and side-effect free so it can be unit-tested without mounting the editor.
33+
*/
34+
export function resolveInitialPolicyContent({
35+
initialVersionId,
36+
versions,
37+
policyContent,
38+
}: ResolveInitialPolicyContentArgs): JSONContent[] {
39+
if (initialVersionId) {
40+
const version = versions.find((v) => v.id === initialVersionId);
41+
if (version) {
42+
return toContentArray(version.content);
43+
}
44+
}
45+
return toContentArray(policyContent);
46+
}
47+
48+
/** Normalize content to an array, matching the editor's existing handling. */
49+
function toContentArray(raw: unknown): JSONContent[] {
50+
return Array.isArray(raw) ? (raw as JSONContent[]) : [raw as JSONContent];
51+
}

0 commit comments

Comments
 (0)