Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions scripts/publication-copy.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copy-only regression checks for #232. These are source-contract tests,
// not proof of rendered behavior, independent verification, or live readiness.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';

const read = path => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');

for (const path of ['src/components/ShareModal.tsx', 'src/components/publish/PublishModal.tsx']) {
test(`${path}: both publication states explain mutable public contents`, () => {
const source = read(path);
assert.match(source, /The public link shows the current list and can change after publication\./);
assert.match(source, /It does not show or verify a sealed snapshot\./);
assert.doesNotMatch(source, /verify who added each item|verifiable DID|with a verifiable/);
});
}

for (const path of ['src/pages/PublicList.tsx', 'src/components/SharedListResource.tsx']) {
test(`${path}: reader labels current data independently of evidence presence`, () => {
const source = read(path);
assert.match(source, /<section aria-label="Publication evidence"/);
assert.match(source, /Live list · not a sealed snapshot/);
assert.match(source, /This page shows the current list, which can change after publication\./);
assert.match(source, /It does not verify a sealed snapshot or who added each item\./);
});
}

test('publish dialog does not promise contributor names absent from its public reader', () => {
const source = read('src/components/publish/PublishModal.tsx');
assert.match(source, /can see the current list contents\. This page does not verify item authorship\./);
assert.doesNotMatch(source, /recorded contributor names|Contributor names will be shown/);
});

test('canonical public reader never treats proof presence or a DID as verification', () => {
const source = read('src/components/SharedListResource.tsx');
assert.doesNotMatch(source, /Cryptographically signed|Verified with/);
assert.match(source, /Supplied proof \(unverified\)/);
assert.match(source, /Declared issuer:/);
assert.match(source, /Identifier \(unverified\):/);
assert.match(source, /These supplied details have not been cryptographically verified by this page\./);
});
9 changes: 7 additions & 2 deletions src/components/ShareModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export function ShareModal({ list, onClose }: ShareModalProps) {
) : (
<>
<p className="text-sm text-gray-600 dark:text-gray-400">
Publish this list with a verifiable <code className="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">did:webvh</code> identity.
Publish this list with a <code className="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">did:webvh</code> identifier.
Anyone with the link can read the list. Publishing does not grant editing access.
</p>

Expand All @@ -262,7 +262,7 @@ export function ShareModal({ list, onClose }: ShareModalProps) {
<div>
<p className="font-medium">What happens when you publish</p>
<ul className="mt-2 text-sm space-y-1 text-amber-700 dark:text-amber-500">
<li>• A verifiable DID is created for the list</li>
<li>• A public resource link identifies the list</li>
<li>• Anyone with the link can read items</li>
<li>• Unpublishing ends public reading; accepted grants remain</li>
</ul>
Expand Down Expand Up @@ -290,6 +290,11 @@ export function ShareModal({ list, onClose }: ShareModalProps) {
</>
)}

<p className="text-sm text-gray-600 dark:text-gray-400">
The public link shows the current list and can change after publication.
It does not show or verify a sealed snapshot.
</p>

<p className="text-sm text-gray-600 dark:text-gray-400">
Removing a named grant does not stop public reading while publication is active. To end public access, unpublish the list. Accepted viewers and editors keep their private access.
</p>
Expand Down
25 changes: 18 additions & 7 deletions src/components/SharedListResource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,14 @@ export function SharedListResource() {
)}
</div>

<section aria-label="Publication evidence" className="mb-4 p-4 bg-gray-100 dark:bg-gray-800 rounded-xl">
<h2 className="text-sm font-medium text-gray-900 dark:text-gray-100">Live list · not a sealed snapshot</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
This page shows the current list, which can change after publication.
It does not verify a sealed snapshot or who added each item.
</p>
</section>

{/* Plan limit hit when saving to favourites */}
{bookmarkPlanLimit && (
<div className="mb-4 p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-xl text-sm space-y-2">
Expand Down Expand Up @@ -368,15 +376,18 @@ export function SharedListResource() {

{/* Provenance */}
{resource.credential?.proof && (
<div className="mt-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-xl">
<div className="flex items-center gap-2 text-green-700 dark:text-green-400 mb-2">
<div className="mt-6 p-4 bg-gray-100 dark:bg-gray-800 rounded-xl">
<div className="flex items-center gap-2 text-gray-700 dark:text-gray-300 mb-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 16v-4m0-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0" />
</svg>
<span className="text-sm font-medium">Cryptographically signed</span>
<span className="text-sm font-medium">Supplied proof (unverified)</span>
</div>
<div className="space-y-1 text-xs text-green-600 dark:text-green-500">
<p><span className="font-medium">Signed by:</span> <span className="font-mono break-all">{resource.credential.issuer}</span></p>
<p className="mb-2 text-xs text-gray-600 dark:text-gray-400">
These supplied details have not been cryptographically verified by this page.
</p>
<div className="space-y-1 text-xs text-gray-600 dark:text-gray-400">
<p><span className="font-medium">Declared issuer:</span> <span className="font-mono break-all">{resource.credential.issuer}</span></p>
<p><span className="font-medium">Date:</span> {new Date(resource.credential.proof.created).toLocaleString()}</p>
<p><span className="font-medium">Cryptosuite:</span> {resource.credential.proof.cryptosuite}</p>
</div>
Expand All @@ -390,7 +401,7 @@ export function SharedListResource() {
<a href="/" className="text-amber-600 hover:text-amber-500">
boop
</a>
{" "}· Verified with{" "}
{" "}· Identifier (unverified):{" "}
<span className="font-mono">did:webvh</span>
</p>
<p className="text-xs text-gray-300 dark:text-gray-600 text-center mt-1 font-mono break-all">
Expand Down
10 changes: 7 additions & 3 deletions src/components/publish/PublishModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Uses Panel component for slide-up drawer experience.
*
* Phase 4: Allows list owners to publish their lists publicly.
* Published lists are verifiable and can be viewed by anyone.
* Published lists can be viewed by anyone; this panel does not verify authorship.
*/

import { useState } from "react";
Expand Down Expand Up @@ -308,7 +308,7 @@ export function PublishModal({ list, onClose }: PublishModalProps) {
<>
<p className="text-sm text-gray-600 dark:text-gray-400">
Publishing makes this list publicly viewable. Anyone with the link
can see the list contents and verify who added each item. Editing requires an accepted editor invitation; publishing does not grant editing access.
can see the current list contents. This page does not verify item authorship. Editing requires an accepted editor invitation; publishing does not grant editing access.
</p>

<div className="p-4 bg-amber-50 dark:bg-amber-900/20 rounded-xl">
Expand All @@ -330,7 +330,6 @@ export function PublishModal({ list, onClose }: PublishModalProps) {
<p className="font-medium">Before you publish</p>
<ul className="mt-2 text-sm space-y-1 text-amber-700 dark:text-amber-500">
<li>• All items will be publicly visible</li>
<li>• Contributor names will be shown</li>
<li>• The list URL will be shareable</li>
</ul>
</div>
Expand All @@ -339,6 +338,11 @@ export function PublishModal({ list, onClose }: PublishModalProps) {
</>
)}

<p className="text-sm text-gray-600 dark:text-gray-400">
The public link shows the current list and can change after publication.
It does not show or verify a sealed snapshot.
</p>

<p className="text-sm text-gray-600 dark:text-gray-400">
Named access is separate from publication. Removing a grant does not stop public reading while publication is active. Unpublishing ends public access; accepted viewers and editors keep their private access.
</p>
Expand Down
11 changes: 10 additions & 1 deletion src/pages/PublicList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Public list view page.
*
* Phase 4: Displays a published list that anyone can view without authentication.
* Shows items with attribution and verification status.
* Shows current items with recorded attribution and unverified identifier details.
*/

import { useEffect } from "react";
Expand Down Expand Up @@ -103,6 +103,15 @@ export function PublicList() {
</div>
</div>

<section aria-label="Publication evidence" className="mb-6 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg">
<h2 className="text-sm font-medium text-gray-900 dark:text-gray-100">Live list · not a sealed snapshot</h2>
<p className="text-sm text-gray-600 dark:text-gray-300">
This page shows the current list, which can change after publication.
It does not verify a sealed snapshot or who added each item.
Contributor names are recorded attribution.
</p>
</section>

{/* Items */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow divide-y divide-gray-100 dark:divide-gray-700">
{items.length === 0 ? (
Expand Down
Loading