@prenta/admin 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/__tests__/lib/seo-service-proposals.test.d.ts +2 -0
- package/dist/__tests__/lib/seo-service-proposals.test.d.ts.map +1 -0
- package/dist/__tests__/lib/seo-service-proposals.test.js +71 -0
- package/dist/__tests__/lib/seo-service-proposals.test.js.map +1 -0
- package/dist/__tests__/router/seo-tab-for-path.test.js +1 -0
- package/dist/__tests__/router/seo-tab-for-path.test.js.map +1 -1
- package/dist/__tests__/views/seo-proposals-tab.render.test.d.ts +2 -0
- package/dist/__tests__/views/seo-proposals-tab.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/seo-proposals-tab.render.test.js +292 -0
- package/dist/__tests__/views/seo-proposals-tab.render.test.js.map +1 -0
- package/dist/components/seo/ChangePreview.d.ts +6 -0
- package/dist/components/seo/ChangePreview.d.ts.map +1 -0
- package/dist/components/seo/ChangePreview.js +18 -0
- package/dist/components/seo/ChangePreview.js.map +1 -0
- package/dist/components/seo/SeoIssueFixPanel.d.ts.map +1 -1
- package/dist/components/seo/SeoIssueFixPanel.js +1 -14
- package/dist/components/seo/SeoIssueFixPanel.js.map +1 -1
- package/dist/lib/seo-service.d.ts +87 -0
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js +36 -0
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/views/SEO.d.ts +5 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +28 -9
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/seo/ProposalsTab.d.ts +7 -0
- package/dist/views/seo/ProposalsTab.d.ts.map +1 -0
- package/dist/views/seo/ProposalsTab.js +298 -0
- package/dist/views/seo/ProposalsTab.js.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/lib/seo-service-proposals.test.ts +79 -0
- package/src/__tests__/router/seo-tab-for-path.test.ts +1 -0
- package/src/__tests__/views/seo-proposals-tab.render.test.tsx +370 -0
- package/src/components/seo/ChangePreview.tsx +47 -0
- package/src/components/seo/SeoIssueFixPanel.tsx +1 -44
- package/src/lib/seo-service.ts +125 -0
- package/src/views/SEO.tsx +40 -7
- package/src/views/seo/ProposalsTab.tsx +681 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @actuate-media/cms-admin
|
|
2
2
|
|
|
3
|
+
## 1.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8b6b25c: AI Proposals Inbox (SEO AI roadmap item 4).
|
|
8
|
+
|
|
9
|
+
**@prenta/core**
|
|
10
|
+
- New routes: `GET /seo/proposals` (unified list of cached issue fixes + open 404 redirect suggestions with staleness, brand-alignment, confidence, and per-feature `allowBulkApply` flags), `POST /seo/proposals/generate` (confirm-gated batch generation, 10 issues per call, 3 concurrent, 20 s budget, halts on `rate_limited`), `POST /seo/proposals/bulk-apply` (max 50; >1 items require `allowBulkApply` for every represented feature → 403 `governance_blocked`; same-document items are applied sequentially against a batch-start snapshot; unreached items return `pending`), `POST /seo/issues/:id/dismiss-fix` (sticky dismissal, audited as `seo_fix_dismissed`).
|
|
11
|
+
- `SeoIssue.metadata.pendingFix` now stores `brandAlignment` (minimum across text fields) and `generatedAt`; `dismissedFingerprint` / `generateSkippedFingerprint` markers keep batch generation from recreating rejected or non-cacheable proposals until the document changes.
|
|
12
|
+
- `SuggestIssueFixResult` gains optional `aiError` (code + message) when the AI path refused or failed.
|
|
13
|
+
- `applyIssueFix` (`seo/issue-fix-apply.ts`) and `acceptRedirectSuggestion` (`redirects/suggestion-accept.ts`) are extracted services; the existing routes keep identical HTTP behaviour.
|
|
14
|
+
- `GET /seo/overview` gains `pendingProposals: { issueFixes, redirects }`.
|
|
15
|
+
- New governance helper `assertBulkApplyAllowed`.
|
|
16
|
+
|
|
17
|
+
**@prenta/admin**
|
|
18
|
+
- New SEO → Proposals tab: multi-select, batch approve (disabled with a Settings link when governance forbids bulk apply), batch dismiss, capped "Generate proposals" loop with Stop, stale-row flagging, "Apply remaining" after a time-budget cut-off. `ChangePreview` is now a shared component.
|
|
19
|
+
|
|
20
|
+
**@prenta/mcp-server**
|
|
21
|
+
- New tools: `list_seo_proposals`, `generate_seo_proposals` (confirm-gated), `bulk_apply_seo_proposals` (confirm-gated), `dismiss_seo_proposal`.
|
|
22
|
+
- `PrentaClientError` now carries the server's machine-readable `code` (e.g. `plan_required`, `governance_blocked`) alongside `status`, and tool error responses append it to the error text as `(code: <code>)` so the calling agent can branch on it.
|
|
23
|
+
|
|
3
24
|
## 1.13.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-service-proposals.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/seo-service-proposals.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const cmsApi = vi.fn();
|
|
3
|
+
vi.mock('../../lib/api.js', () => ({ cmsApi: (...args) => cmsApi(...args) }));
|
|
4
|
+
const svc = await import('../../lib/seo-service.js');
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// Block body on purpose: `mockReset()` returns the mock, and vitest would
|
|
7
|
+
// run a returned function as after-test cleanup.
|
|
8
|
+
cmsApi.mockReset();
|
|
9
|
+
});
|
|
10
|
+
describe('proposal service helpers', () => {
|
|
11
|
+
it('fetchSeoProposals GETs /seo/proposals and throws on error', async () => {
|
|
12
|
+
cmsApi.mockResolvedValueOnce({ status: 500, error: 'boom' });
|
|
13
|
+
await expect(svc.fetchSeoProposals()).rejects.toThrow('boom');
|
|
14
|
+
cmsApi.mockResolvedValueOnce({
|
|
15
|
+
status: 200,
|
|
16
|
+
data: {
|
|
17
|
+
proposals: [],
|
|
18
|
+
counts: { issueFixes: 0, redirects: 0, stale: 0, eligibleForGeneration: 0 },
|
|
19
|
+
governance: { issueFixBulk: false, redirectBulk: false },
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
await expect(svc.fetchSeoProposals()).resolves.toMatchObject({ proposals: [] });
|
|
23
|
+
expect(cmsApi).toHaveBeenLastCalledWith('/seo/proposals');
|
|
24
|
+
});
|
|
25
|
+
it('generateSeoProposals POSTs confirm: true and returns the result or error', async () => {
|
|
26
|
+
cmsApi.mockResolvedValueOnce({
|
|
27
|
+
status: 200,
|
|
28
|
+
data: { generated: 3, skipped: 1, failed: 0, remaining: 2, haltedReason: null },
|
|
29
|
+
});
|
|
30
|
+
await expect(svc.generateSeoProposals()).resolves.toEqual({
|
|
31
|
+
result: { generated: 3, skipped: 1, failed: 0, remaining: 2, haltedReason: null },
|
|
32
|
+
});
|
|
33
|
+
expect(cmsApi).toHaveBeenCalledWith('/seo/proposals/generate', {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
body: JSON.stringify({ confirm: true }),
|
|
36
|
+
});
|
|
37
|
+
cmsApi.mockResolvedValueOnce({ status: 429, error: 'AI rate limit reached.' });
|
|
38
|
+
await expect(svc.generateSeoProposals()).resolves.toEqual({ error: 'AI rate limit reached.' });
|
|
39
|
+
});
|
|
40
|
+
it('bulkApplySeoProposals flags governance_blocked distinctly', async () => {
|
|
41
|
+
cmsApi.mockResolvedValueOnce({
|
|
42
|
+
status: 403,
|
|
43
|
+
error: 'Enable "Allow bulk apply"',
|
|
44
|
+
code: 'governance_blocked',
|
|
45
|
+
});
|
|
46
|
+
await expect(svc.bulkApplySeoProposals([{ kind: 'issue-fix', id: 'i1', fingerprint: 'f' }])).resolves.toEqual({ error: 'Enable "Allow bulk apply"', governanceBlocked: true });
|
|
47
|
+
cmsApi.mockResolvedValueOnce({
|
|
48
|
+
status: 200,
|
|
49
|
+
data: {
|
|
50
|
+
results: [{ kind: 'issue-fix', id: 'i1', status: 'applied' }],
|
|
51
|
+
applied: 1,
|
|
52
|
+
failed: 0,
|
|
53
|
+
pending: 0,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
await expect(svc.bulkApplySeoProposals([{ kind: 'issue-fix', id: 'i1' }])).resolves.toEqual({
|
|
57
|
+
result: {
|
|
58
|
+
results: [{ kind: 'issue-fix', id: 'i1', status: 'applied' }],
|
|
59
|
+
applied: 1,
|
|
60
|
+
failed: 0,
|
|
61
|
+
pending: 0,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it('dismissSeoIssueFix POSTs to the encoded issue path', async () => {
|
|
66
|
+
cmsApi.mockResolvedValueOnce({ status: 200, data: { dismissed: true } });
|
|
67
|
+
await expect(svc.dismissSeoIssueFix('a/b')).resolves.toEqual({});
|
|
68
|
+
expect(cmsApi).toHaveBeenCalledWith('/seo/issues/a%2Fb/dismiss-fix', { method: 'POST' });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=seo-service-proposals.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-service-proposals.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/seo-service-proposals.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ describe('seoTabForPath', () => {
|
|
|
15
15
|
expect(seoTabForPath('/seo/links')).toBe('technical');
|
|
16
16
|
expect(seoTabForPath('/seo/redirects')).toBe('redirects');
|
|
17
17
|
expect(seoTabForPath('/seo/audit')).toBe('audit');
|
|
18
|
+
expect(seoTabForPath('/seo/proposals')).toBe('proposals');
|
|
18
19
|
});
|
|
19
20
|
it('returns null for non-SEO paths so they fall through', () => {
|
|
20
21
|
expect(seoTabForPath('/dashboard')).toBeNull();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-tab-for-path.test.js","sourceRoot":"","sources":["../../../src/__tests__/router/seo-tab-for-path.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvD,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"seo-tab-for-path.test.js","sourceRoot":"","sources":["../../../src/__tests__/router/seo-tab-for-path.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvD,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACrD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACjD,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC9C,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC1C,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC3C,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,2EAA2E;QAC3E,wEAAwE;QACxE,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YACtC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-proposals-tab.render.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/views/seo-proposals-tab.render.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
function proposal(over = {}) {
|
|
6
|
+
return {
|
|
7
|
+
id: 'issue:i1',
|
|
8
|
+
kind: 'issue-fix',
|
|
9
|
+
title: 'Duplicate meta title',
|
|
10
|
+
entity: { type: 'page', id: 'd1', title: 'Pricing', url: '/pricing' },
|
|
11
|
+
changes: [{ field: 'metaTitle', label: 'Meta title', before: 'Dup', after: 'Unique Pricing' }],
|
|
12
|
+
justification: 'AI drafted a unique title.',
|
|
13
|
+
source: 'ai',
|
|
14
|
+
confidence: null,
|
|
15
|
+
brandAlignment: 88,
|
|
16
|
+
generatedAt: new Date(Date.now() - 3600_000).toISOString(),
|
|
17
|
+
fingerprint: 'fp-1',
|
|
18
|
+
stale: false,
|
|
19
|
+
issueType: 'duplicate-meta-title',
|
|
20
|
+
severity: 'warning',
|
|
21
|
+
issueId: 'i1',
|
|
22
|
+
suggestionId: null,
|
|
23
|
+
...over,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function payload(over = {}) {
|
|
27
|
+
return {
|
|
28
|
+
proposals: [
|
|
29
|
+
proposal(),
|
|
30
|
+
proposal({
|
|
31
|
+
id: 'issue:i2',
|
|
32
|
+
issueId: 'i2',
|
|
33
|
+
fingerprint: 'fp-2',
|
|
34
|
+
title: 'Missing description',
|
|
35
|
+
brandAlignment: 60,
|
|
36
|
+
}),
|
|
37
|
+
proposal({
|
|
38
|
+
id: 'redirect:s1',
|
|
39
|
+
kind: 'redirect',
|
|
40
|
+
title: 'Redirect /old → /new',
|
|
41
|
+
source: 'ai-404-recovery',
|
|
42
|
+
confidence: 0.82,
|
|
43
|
+
brandAlignment: null,
|
|
44
|
+
fingerprint: null,
|
|
45
|
+
issueId: null,
|
|
46
|
+
suggestionId: 's1',
|
|
47
|
+
severity: null,
|
|
48
|
+
changes: [{ field: 'redirect', label: 'Redirect', before: '/old', after: '/new' }],
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
counts: { issueFixes: 2, redirects: 1, stale: 0, eligibleForGeneration: 5 },
|
|
52
|
+
governance: { issueFixBulk: true, redirectBulk: true },
|
|
53
|
+
...over,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const fetchSeoProposals = vi.fn(async () => payload());
|
|
57
|
+
const generateSeoProposals = vi.fn(async () => ({
|
|
58
|
+
result: { generated: 2, skipped: 0, failed: 0, remaining: 0, haltedReason: null },
|
|
59
|
+
}));
|
|
60
|
+
const bulkApplySeoProposals = vi.fn(async (_items) => ({
|
|
61
|
+
result: { results: [], applied: 0, failed: 0, pending: 0 },
|
|
62
|
+
}));
|
|
63
|
+
const dismissSeoIssueFix = vi.fn(async (_id) => ({}));
|
|
64
|
+
const dismissRedirectSuggestion = vi.fn(async (_id) => ({}));
|
|
65
|
+
vi.mock('../../lib/seo-service.js', () => ({
|
|
66
|
+
fetchSeoProposals: () => fetchSeoProposals(),
|
|
67
|
+
generateSeoProposals: () => generateSeoProposals(),
|
|
68
|
+
bulkApplySeoProposals: (items) => bulkApplySeoProposals(items),
|
|
69
|
+
dismissSeoIssueFix: (id) => dismissSeoIssueFix(id),
|
|
70
|
+
dismissRedirectSuggestion: (id) => dismissRedirectSuggestion(id),
|
|
71
|
+
}));
|
|
72
|
+
vi.mock('sonner', () => ({
|
|
73
|
+
toast: { success: vi.fn(), error: vi.fn(), info: vi.fn(), loading: vi.fn() },
|
|
74
|
+
}));
|
|
75
|
+
const { ProposalsTab } = await import('../../views/seo/ProposalsTab.js');
|
|
76
|
+
const { toast } = await import('sonner');
|
|
77
|
+
function deferred() {
|
|
78
|
+
let resolve;
|
|
79
|
+
let reject;
|
|
80
|
+
const promise = new Promise((res, rej) => {
|
|
81
|
+
resolve = res;
|
|
82
|
+
reject = rej;
|
|
83
|
+
});
|
|
84
|
+
return { promise, resolve, reject };
|
|
85
|
+
}
|
|
86
|
+
/** Let any already-settled promise continuations run. */
|
|
87
|
+
async function flushMicrotasks() {
|
|
88
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
89
|
+
}
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
fetchSeoProposals.mockClear();
|
|
92
|
+
fetchSeoProposals.mockImplementation(async () => payload());
|
|
93
|
+
generateSeoProposals.mockClear();
|
|
94
|
+
bulkApplySeoProposals.mockClear();
|
|
95
|
+
dismissSeoIssueFix.mockClear();
|
|
96
|
+
dismissRedirectSuggestion.mockClear();
|
|
97
|
+
vi.mocked(toast.success).mockClear();
|
|
98
|
+
vi.mocked(toast.error).mockClear();
|
|
99
|
+
});
|
|
100
|
+
describe('ProposalsTab', () => {
|
|
101
|
+
it('renders rows with kind, badges, and expandable change preview', async () => {
|
|
102
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
103
|
+
expect(await screen.findByText('Duplicate meta title')).toBeTruthy();
|
|
104
|
+
expect(screen.getByText('82%')).toBeTruthy();
|
|
105
|
+
expect(screen.getByLabelText('Brand voice score 88 out of 100')).toBeTruthy();
|
|
106
|
+
fireEvent.click(screen.getByRole('button', { name: 'Show changes for Duplicate meta title' }));
|
|
107
|
+
expect(screen.getByRole('table', { name: 'Proposed SEO changes' })).toBeTruthy();
|
|
108
|
+
expect(screen.getByText('Unique Pricing')).toBeTruthy();
|
|
109
|
+
});
|
|
110
|
+
it('select-all selects only live rows and shows the bulk bar', async () => {
|
|
111
|
+
fetchSeoProposals.mockImplementationOnce(async () => payload({
|
|
112
|
+
proposals: [
|
|
113
|
+
proposal(),
|
|
114
|
+
proposal({ id: 'issue:stale', issueId: 'stale', stale: true, title: 'Stale one' }),
|
|
115
|
+
],
|
|
116
|
+
counts: { issueFixes: 1, redirects: 0, stale: 1, eligibleForGeneration: 1 },
|
|
117
|
+
}));
|
|
118
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
119
|
+
await screen.findByText('Stale one');
|
|
120
|
+
expect(screen.getByText('Stale — regenerate')).toBeTruthy();
|
|
121
|
+
expect(screen.queryByLabelText('Select proposal Stale one')).toBeNull();
|
|
122
|
+
fireEvent.click(screen.getByLabelText('Select all proposals'));
|
|
123
|
+
expect(screen.getByRole('region', { name: 'Bulk actions' }).textContent).toContain('1 selected');
|
|
124
|
+
});
|
|
125
|
+
it('disables batch approve with a settings callout when governance forbids bulk apply', async () => {
|
|
126
|
+
fetchSeoProposals.mockImplementationOnce(async () => payload({ governance: { issueFixBulk: false, redirectBulk: true } }));
|
|
127
|
+
const onNavigate = vi.fn();
|
|
128
|
+
render(_jsx(ProposalsTab, { planInfo: null, onNavigate: onNavigate }));
|
|
129
|
+
await screen.findByText('Duplicate meta title');
|
|
130
|
+
fireEvent.click(screen.getByLabelText('Select proposal Duplicate meta title'));
|
|
131
|
+
fireEvent.click(screen.getByLabelText('Select proposal Missing description'));
|
|
132
|
+
const approve = screen.getByRole('button', { name: 'Approve selected' });
|
|
133
|
+
expect(approve).toHaveProperty('disabled', true);
|
|
134
|
+
expect(screen.getByText(/Allow bulk apply/)).toBeTruthy();
|
|
135
|
+
expect(screen.queryByRole('link')).toBeNull();
|
|
136
|
+
fireEvent.click(screen.getByRole('button', { name: 'Open Settings → AI' }));
|
|
137
|
+
expect(onNavigate).toHaveBeenCalledWith('/settings?tab=ai');
|
|
138
|
+
});
|
|
139
|
+
it('renders the governance callout without a settings control when onNavigate is absent', async () => {
|
|
140
|
+
fetchSeoProposals.mockImplementationOnce(async () => payload({ governance: { issueFixBulk: false, redirectBulk: true } }));
|
|
141
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
142
|
+
await screen.findByText('Duplicate meta title');
|
|
143
|
+
fireEvent.click(screen.getByLabelText('Select all proposals'));
|
|
144
|
+
expect(screen.getByText(/Allow bulk apply/)).toBeTruthy();
|
|
145
|
+
expect(screen.queryByRole('button', { name: 'Open Settings → AI' })).toBeNull();
|
|
146
|
+
expect(screen.queryByRole('link')).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
it('issues a single bulk-apply request when approve is triggered twice while in flight', async () => {
|
|
149
|
+
const inflight = deferred();
|
|
150
|
+
bulkApplySeoProposals.mockImplementationOnce(() => inflight.promise);
|
|
151
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
152
|
+
await screen.findByText('Duplicate meta title');
|
|
153
|
+
const approve = screen.getByRole('button', { name: 'Approve Duplicate meta title' });
|
|
154
|
+
fireEvent.click(approve);
|
|
155
|
+
fireEvent.click(approve);
|
|
156
|
+
fireEvent.click(screen.getByRole('button', { name: 'Approve Missing description' }));
|
|
157
|
+
await flushMicrotasks();
|
|
158
|
+
expect(bulkApplySeoProposals).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(approve).toHaveProperty('disabled', true);
|
|
160
|
+
inflight.resolve({ result: { results: [], applied: 0, failed: 0, pending: 0 } });
|
|
161
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Approve Duplicate meta title' })).toHaveProperty('disabled', false));
|
|
162
|
+
});
|
|
163
|
+
it('disables every other mutation control while an apply is in flight', async () => {
|
|
164
|
+
const inflight = deferred();
|
|
165
|
+
bulkApplySeoProposals.mockImplementationOnce(() => inflight.promise);
|
|
166
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
167
|
+
await screen.findByText('Duplicate meta title');
|
|
168
|
+
fireEvent.click(screen.getByLabelText('Select all proposals'));
|
|
169
|
+
fireEvent.click(screen.getByRole('button', { name: 'Approve selected' }));
|
|
170
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Apply 3 proposals' }));
|
|
171
|
+
await waitFor(() => expect(bulkApplySeoProposals).toHaveBeenCalledTimes(1));
|
|
172
|
+
expect(screen.getByRole('button', { name: 'Dismiss selected' })).toHaveProperty('disabled', true);
|
|
173
|
+
expect(screen.getByRole('button', { name: 'Approve selected' })).toHaveProperty('disabled', true);
|
|
174
|
+
expect(screen.getByRole('button', { name: 'Dismiss Duplicate meta title' })).toHaveProperty('disabled', true);
|
|
175
|
+
expect(screen.getByRole('button', { name: 'Generate proposals (5 eligible)' })).toHaveProperty('disabled', true);
|
|
176
|
+
fireEvent.click(screen.getByRole('button', { name: 'Dismiss selected' }));
|
|
177
|
+
expect(dismissSeoIssueFix).not.toHaveBeenCalled();
|
|
178
|
+
inflight.resolve({ result: { results: [], applied: 0, failed: 0, pending: 0 } });
|
|
179
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Generate proposals (5 eligible)' })).toHaveProperty('disabled', false));
|
|
180
|
+
});
|
|
181
|
+
it('re-enables controls and toasts when the apply request throws', async () => {
|
|
182
|
+
bulkApplySeoProposals.mockRejectedValueOnce(new Error('network down'));
|
|
183
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
184
|
+
await screen.findByText('Duplicate meta title');
|
|
185
|
+
fireEvent.click(screen.getByRole('button', { name: 'Approve Duplicate meta title' }));
|
|
186
|
+
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('network down'));
|
|
187
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Approve Duplicate meta title' })).toHaveProperty('disabled', false));
|
|
188
|
+
expect(screen.getByRole('button', { name: 'Dismiss Duplicate meta title' })).toHaveProperty('disabled', false);
|
|
189
|
+
});
|
|
190
|
+
it('stops the generate loop and stays silent after unmount', async () => {
|
|
191
|
+
const firstPass = deferred();
|
|
192
|
+
generateSeoProposals.mockImplementationOnce(() => firstPass.promise);
|
|
193
|
+
const { unmount } = render(_jsx(ProposalsTab, { planInfo: null }));
|
|
194
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Generate proposals (5 eligible)' }));
|
|
195
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Generate' }));
|
|
196
|
+
await waitFor(() => expect(generateSeoProposals).toHaveBeenCalledTimes(1));
|
|
197
|
+
unmount();
|
|
198
|
+
firstPass.resolve({
|
|
199
|
+
result: { generated: 1, skipped: 0, failed: 0, remaining: 1, haltedReason: null },
|
|
200
|
+
});
|
|
201
|
+
await flushMicrotasks();
|
|
202
|
+
await flushMicrotasks();
|
|
203
|
+
expect(generateSeoProposals).toHaveBeenCalledTimes(1);
|
|
204
|
+
expect(toast.success).not.toHaveBeenCalled();
|
|
205
|
+
expect(toast.error).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
it('confirming batch approve calls bulkApplySeoProposals with selected items and reports partial results', async () => {
|
|
208
|
+
bulkApplySeoProposals.mockResolvedValueOnce({
|
|
209
|
+
result: {
|
|
210
|
+
results: [
|
|
211
|
+
{ kind: 'issue-fix', id: 'i1', status: 'applied' },
|
|
212
|
+
{ kind: 'issue-fix', id: 'i2', status: 'stale', reason: 'stale' },
|
|
213
|
+
],
|
|
214
|
+
applied: 1,
|
|
215
|
+
failed: 1,
|
|
216
|
+
pending: 0,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const { toast } = await import('sonner');
|
|
220
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
221
|
+
await screen.findByText('Duplicate meta title');
|
|
222
|
+
fireEvent.click(screen.getByLabelText('Select proposal Duplicate meta title'));
|
|
223
|
+
fireEvent.click(screen.getByLabelText('Select proposal Missing description'));
|
|
224
|
+
fireEvent.click(screen.getByRole('button', { name: 'Approve selected' }));
|
|
225
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Apply 2 proposals' }));
|
|
226
|
+
await waitFor(() => expect(bulkApplySeoProposals).toHaveBeenCalledWith([
|
|
227
|
+
{ kind: 'issue-fix', id: 'i1', fingerprint: 'fp-1' },
|
|
228
|
+
{ kind: 'issue-fix', id: 'i2', fingerprint: 'fp-2' },
|
|
229
|
+
]));
|
|
230
|
+
await waitFor(() => expect(toast.success).toHaveBeenCalledWith('1 proposal applied.'));
|
|
231
|
+
expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('1 could not be applied'));
|
|
232
|
+
});
|
|
233
|
+
it('offers "Apply remaining" when the server reports pending items', async () => {
|
|
234
|
+
bulkApplySeoProposals.mockResolvedValueOnce({
|
|
235
|
+
result: {
|
|
236
|
+
results: [
|
|
237
|
+
{ kind: 'issue-fix', id: 'i1', status: 'applied' },
|
|
238
|
+
{ kind: 'issue-fix', id: 'i2', status: 'pending' },
|
|
239
|
+
],
|
|
240
|
+
applied: 1,
|
|
241
|
+
failed: 0,
|
|
242
|
+
pending: 1,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
246
|
+
await screen.findByText('Duplicate meta title');
|
|
247
|
+
fireEvent.click(screen.getByLabelText('Select all proposals'));
|
|
248
|
+
fireEvent.click(screen.getByRole('button', { name: 'Approve selected' }));
|
|
249
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Apply 3 proposals' }));
|
|
250
|
+
expect(await screen.findByRole('button', { name: 'Apply remaining (1)' })).toBeTruthy();
|
|
251
|
+
});
|
|
252
|
+
it('dismiss selected fans out to the per-kind dismiss helpers', async () => {
|
|
253
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
254
|
+
await screen.findByText('Duplicate meta title');
|
|
255
|
+
fireEvent.click(screen.getByLabelText('Select all proposals'));
|
|
256
|
+
fireEvent.click(screen.getByRole('button', { name: 'Dismiss selected' }));
|
|
257
|
+
await waitFor(() => expect(dismissSeoIssueFix).toHaveBeenCalledTimes(2));
|
|
258
|
+
expect(dismissRedirectSuggestion).toHaveBeenCalledWith('s1');
|
|
259
|
+
});
|
|
260
|
+
it('generate loops while remaining > 0 and shows the eligible count', async () => {
|
|
261
|
+
generateSeoProposals
|
|
262
|
+
.mockResolvedValueOnce({
|
|
263
|
+
result: { generated: 10, skipped: 0, failed: 0, remaining: 3, haltedReason: null },
|
|
264
|
+
})
|
|
265
|
+
.mockResolvedValueOnce({
|
|
266
|
+
result: { generated: 3, skipped: 0, failed: 0, remaining: 0, haltedReason: null },
|
|
267
|
+
});
|
|
268
|
+
const { toast } = await import('sonner');
|
|
269
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
270
|
+
const button = await screen.findByRole('button', { name: 'Generate proposals (5 eligible)' });
|
|
271
|
+
fireEvent.click(button);
|
|
272
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Generate' }));
|
|
273
|
+
await waitFor(() => expect(generateSeoProposals).toHaveBeenCalledTimes(2));
|
|
274
|
+
await waitFor(() => expect(toast.success).toHaveBeenCalledWith('13 proposals generated.'));
|
|
275
|
+
});
|
|
276
|
+
it('disables Generate when nothing is eligible and shows the empty state', async () => {
|
|
277
|
+
fetchSeoProposals.mockImplementationOnce(async () => payload({
|
|
278
|
+
proposals: [],
|
|
279
|
+
counts: { issueFixes: 0, redirects: 0, stale: 0, eligibleForGeneration: 0 },
|
|
280
|
+
}));
|
|
281
|
+
render(_jsx(ProposalsTab, { planInfo: null }));
|
|
282
|
+
expect(await screen.findByText('No proposals yet')).toBeTruthy();
|
|
283
|
+
expect(screen.getByRole('button', { name: 'Generate proposals (0 eligible)' })).toHaveProperty('disabled', true);
|
|
284
|
+
});
|
|
285
|
+
it('hides Generate behind the plan callout when seo.inlineApply is off', async () => {
|
|
286
|
+
render(_jsx(ProposalsTab, { planInfo: { tier: 'starter', features: [], upgradeUrl: null } }));
|
|
287
|
+
await screen.findByText('Duplicate meta title');
|
|
288
|
+
expect(screen.queryByRole('button', { name: /Generate proposals/ })).toBeNull();
|
|
289
|
+
expect(screen.getByText(/Upgrade to unlock SEO inline apply/)).toBeTruthy();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
//# sourceMappingURL=seo-proposals-tab.render.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-proposals-tab.render.test.js","sourceRoot":"","sources":["../../../src/__tests__/views/seo-proposals-tab.render.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SeoFixFieldChange } from '../../lib/seo-service.js';
|
|
2
|
+
/** Before/after table for a proposed SEO change set. Shared by the issue fix panel and the proposals inbox. */
|
|
3
|
+
export declare function ChangePreview({ changes }: {
|
|
4
|
+
changes: SeoFixFieldChange[];
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
6
|
+
//# sourceMappingURL=ChangePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChangePreview.d.ts","sourceRoot":"","sources":["../../../src/components/seo/ChangePreview.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AASjE,+GAA+G;AAC/G,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAAE,kDAkC1E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
function formatValue(value) {
|
|
4
|
+
if (value === null || value === '')
|
|
5
|
+
return '—';
|
|
6
|
+
if (typeof value === 'boolean')
|
|
7
|
+
return value ? 'Yes' : 'No';
|
|
8
|
+
if (value.length > 280)
|
|
9
|
+
return `${value.slice(0, 277)}…`;
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/** Before/after table for a proposed SEO change set. Shared by the issue fix panel and the proposals inbox. */
|
|
13
|
+
export function ChangePreview({ changes }) {
|
|
14
|
+
if (changes.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
return (_jsx("div", { className: "border-border overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full min-w-[480px] text-sm", "aria-label": "Proposed SEO changes", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-border bg-muted/40 text-muted-foreground border-b text-left", children: [_jsx("th", { scope: "col", className: "py-2 pr-3 pl-3 font-medium", children: "Field" }), _jsx("th", { scope: "col", className: "py-2 pr-3 font-medium", children: "Current" }), _jsx("th", { scope: "col", className: "py-2 pr-3 font-medium", children: "Proposed" })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: changes.map((c) => (_jsxs("tr", { children: [_jsx("td", { className: "text-foreground py-2 pr-3 pl-3 font-medium", children: c.label }), _jsx("td", { className: "text-muted-foreground max-w-48 py-2 pr-3 wrap-break-word", children: formatValue(c.before) }), _jsx("td", { className: "text-foreground max-w-48 py-2 pr-3 wrap-break-word", children: formatValue(c.after) })] }, c.field))) })] }) }));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=ChangePreview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChangePreview.js","sourceRoot":"","sources":["../../../src/components/seo/ChangePreview.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAIZ,SAAS,WAAW,CAAC,KAA8B;IACjD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,GAAG,CAAA;IAC9C,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3D,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAA;IACxD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,aAAa,CAAC,EAAE,OAAO,EAAoC;IACzE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACrC,OAAO,CACL,cAAK,SAAS,EAAC,iDAAiD,YAC9D,iBAAO,SAAS,EAAC,8BAA8B,gBAAY,sBAAsB,aAC/E,0BACE,cAAI,SAAS,EAAC,oEAAoE,aAChF,aAAI,KAAK,EAAC,KAAK,EAAC,SAAS,EAAC,4BAA4B,sBAEjD,EACL,aAAI,KAAK,EAAC,KAAK,EAAC,SAAS,EAAC,uBAAuB,wBAE5C,EACL,aAAI,KAAK,EAAC,KAAK,EAAC,SAAS,EAAC,uBAAuB,yBAE5C,IACF,GACC,EACR,gBAAO,SAAS,EAAC,wBAAwB,YACtC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAClB,yBACE,aAAI,SAAS,EAAC,4CAA4C,YAAE,CAAC,CAAC,KAAK,GAAM,EACzE,aAAI,SAAS,EAAC,0DAA0D,YACrE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,GACnB,EACL,aAAI,SAAS,EAAC,oDAAoD,YAC/D,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,GAClB,KAPE,CAAC,CAAC,KAAK,CAQX,CACN,CAAC,GACI,IACF,GACJ,CACP,CAAA;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SeoIssueFixPanel.d.ts","sourceRoot":"","sources":["../../../src/components/seo/SeoIssueFixPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,
|
|
1
|
+
{"version":3,"file":"SeoIssueFixPanel.d.ts","sourceRoot":"","sources":["../../../src/components/seo/SeoIssueFixPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,EAaL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AA0LjE,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,aAAa,EACb,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,QAAQ,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;CAC3B,kDAkWA;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,OAAO,EACP,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,2CAeA"}
|
|
@@ -6,6 +6,7 @@ import { toast } from 'sonner';
|
|
|
6
6
|
import { applySeoIssueFix, fetchSeoIssueFixSuggestion, fetchSeoContentBrief, fetchSeoContentResearch, generateSeoContentBrief, ignoreSeoIssue, isInlineSeoFixIssue, researchAndBriefSeoIssue, supportsSeoContentBrief, supportsSeoContentResearch, } from '../../lib/seo-service.js';
|
|
7
7
|
import { hasPlanFeature } from '../../lib/plan.js';
|
|
8
8
|
import { PlanUpgradeCallout } from '../PlanUpgradeCallout.js';
|
|
9
|
+
import { ChangePreview } from './ChangePreview.js';
|
|
9
10
|
import { SeoErrorState, btnPrimary, btnSecondary } from './primitives.js';
|
|
10
11
|
function approveButtonLabel(strategy) {
|
|
11
12
|
switch (strategy) {
|
|
@@ -37,20 +38,6 @@ function approveSuccessToast(strategy) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
function formatValue(value) {
|
|
41
|
-
if (value === null || value === '')
|
|
42
|
-
return '—';
|
|
43
|
-
if (typeof value === 'boolean')
|
|
44
|
-
return value ? 'Yes' : 'No';
|
|
45
|
-
if (value.length > 280)
|
|
46
|
-
return `${value.slice(0, 277)}…`;
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
function ChangePreview({ changes }) {
|
|
50
|
-
if (changes.length === 0)
|
|
51
|
-
return null;
|
|
52
|
-
return (_jsx("div", { className: "border-border overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full min-w-[480px] text-sm", "aria-label": "Proposed SEO changes", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-border bg-muted/40 text-muted-foreground border-b text-left", children: [_jsx("th", { scope: "col", className: "py-2 pr-3 pl-3 font-medium", children: "Field" }), _jsx("th", { scope: "col", className: "py-2 pr-3 font-medium", children: "Current" }), _jsx("th", { scope: "col", className: "py-2 pr-3 font-medium", children: "Proposed" })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: changes.map((c) => (_jsxs("tr", { children: [_jsx("td", { className: "text-foreground py-2 pr-3 pl-3 font-medium", children: c.label }), _jsx("td", { className: "text-muted-foreground max-w-48 py-2 pr-3 wrap-break-word", children: formatValue(c.before) }), _jsx("td", { className: "text-foreground max-w-48 py-2 pr-3 wrap-break-word", children: formatValue(c.after) })] }, c.field))) })] }) }));
|
|
53
|
-
}
|
|
54
41
|
function ContentResearchCard({ research }) {
|
|
55
42
|
return (_jsxs("div", { className: "border-border bg-card space-y-3 rounded-md border p-4", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "bg-accent text-foreground inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium", children: [_jsx(Sparkles, { className: "h-3 w-3", "aria-hidden": true }), "Topic research"] }) }), _jsx("p", { className: "text-foreground text-sm", children: research.summary }), _jsxs("ul", { className: "text-foreground space-y-1.5 text-sm", children: [research.focusKeyword ? (_jsxs("li", { children: [_jsx("span", { className: "text-muted-foreground", children: "Focus keyword: " }), research.focusKeyword] })) : null, research.suggestedWordCount ? (_jsxs("li", { children: [_jsx("span", { className: "text-muted-foreground", children: "Suggested length: " }), "~", research.suggestedWordCount, " words"] })) : null] }), research.outline.length > 0 ? (_jsxs("div", { children: [_jsx("p", { className: "text-muted-foreground mb-1.5 text-xs font-medium tracking-wide uppercase", children: "Suggested outline" }), _jsx("ul", { className: "text-foreground list-disc space-y-1 pl-5 text-sm", children: research.outline.map((item) => (_jsx("li", { children: item }, item))) })] })) : null, research.faqCandidates.length > 0 ? (_jsxs("div", { children: [_jsx("p", { className: "text-muted-foreground mb-1.5 text-xs font-medium tracking-wide uppercase", children: "FAQ candidates" }), _jsx("ul", { className: "text-foreground list-disc space-y-1 pl-5 text-sm", children: research.faqCandidates.map((q) => (_jsx("li", { children: q }, q))) })] })) : null] }));
|
|
56
43
|
}
|