openvibers/openvibe.wiki Commits

Wikis, not spaces: every word a reader sees says wiki (the nav, the home, start/settings/delete, titles, llms.txt), since openvibe.space is another product; the addresses stay, with /wikis and /new-wiki as aliases (#79)

OP OpenVibers committed 27 days ago · 0ef9dc340e · 6 files, +51 −48

Changes

ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 78ea2a5..9de9f9c 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -77,7 +77,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
const errorPage = (req, res, status, title, message) => send(req, res, status, views.errorBody({ status, title, message }), { title, robots: 'noindex, nofollow' });
const notFound = (req, res) => errorPage(req, res, 404, 'Page not found', 'Nothing lives at that address, or it is not visible to you.');
const gone = (req, res) => errorPage(req, res, 410, 'Gone', 'This page was deleted. Its history is kept, but it is no longer published.');
-- // VIP (WS-K task 8): the join prompt for a VIP space or page the viewer is not admitted to (403, never cached).
++ // VIP (WS-K task 8): the join prompt for a VIP wiki or page the viewer is not admitted to (403, never cached).
const vipGate = (req, res, space, page) => {
const r = svc.access.vipRefusal(space, page, req.actor);
if (!r) return false;
@@@ -123,7 +123,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
router.get('/', wrap(async (req, res) => {
if (req.actor.subject) await svc.access.prepareVip(req.actor, (await svc.vipSpaces()).map((space) => ({ space }))); // VIP spaces the viewer may see
const [spaces, recent] = await Promise.all([svc.listSpaces(req.actor), svc.recentChanges(10)]);
-- // The official spaces' shelves: what is inside them, on the front page (only public spaces: the home is cached).
++ // The official wikis' shelves: what is inside them, on the front page (only public spaces: the home is cached).
const shelves = new Map(await Promise.all(spaces.filter((s) => s.kind === 'official' && s.visibility === 'public').slice(0, 3)
.map(async (s) => [s.id, await svc.shelf(s, req.actor, 48)])));
// The site's one-line AI summary: the shell turns it into the ai-summary meta and the home's
@@@ -142,19 +142,22 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
}));
// ── Spaces ───────────────────────────────────────────────
++ // Wikis were called spaces until 2026-10-17 (openvibe.space is another product); the addresses stay, these are aliases.
++ router.get('/wikis', (req, res) => res.redirect(301, '/'));
++ router.get('/new-wiki', (req, res) => res.redirect(301, '/new-space'));
router.get('/new-space', (req, res) => {
-- if (!req.actor.subject) return needSignIn(req, res, 'Sign in with your OpenVibe account to start a space.');
-- send(req, res, 200, views.newSpacePage({ staff: req.actor.staff }), { title: 'Start a space', robots: 'noindex, nofollow' });
++ if (!req.actor.subject) return needSignIn(req, res, 'Sign in with your OpenVibe account to start a wiki.');
++ send(req, res, 200, views.newSpacePage({ staff: req.actor.staff }), { title: 'Start a wiki', robots: 'noindex, nofollow' });
});
router.post('/new-space', B('wiki.space.create'), form, wrap(async (req, res) => {
-- if (!req.actor.subject) return needSignIn(req, res, 'Sign in with your OpenVibe account to start a space.');
++ if (!req.actor.subject) return needSignIn(req, res, 'Sign in with your OpenVibe account to start a wiki.');
const b = req.body || {};
try {
const space = await svc.createSpace({ name: b.name, slug: b.slug || null, description: b.description || null, visibility: b.visibility, kind: b.official === '1' && req.actor.staff ? 'official' : 'user' }, req.actor);
res.redirect(303, `/s/${encodeURIComponent(space.slug)}`);
} catch (err) {
if (!err.status || err.status >= 500) throw err;
-- send(req, res, err.status, views.newSpacePage({ values: b, error: err.message, staff: req.actor.staff }), { title: 'Start a space', robots: 'noindex, nofollow' });
++ send(req, res, err.status, views.newSpacePage({ values: b, error: err.message, staff: req.actor.staff }), { title: 'Start a wiki', robots: 'noindex, nofollow' });
}
}));
@@@ -165,7 +168,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
const proposals = canEdit ? (await svc.pendingProposals(req.actor)).filter((p) => p.space_id === space.id) : [];
const open = space.visibility === 'public';
send(req, res, 200, views.spacePage({ space, tree: await svc.tree(space, req.actor), canEdit, canManage: svc.access.canManage(space, req.actor), proposals }), {
-- title: space.name, description: space.description || `${space.name}: a space on OpenVibe.Wiki.`, path: svc.spacePath(space),
++ title: space.name, description: space.description || `${space.name}: a wiki on OpenVibe.Wiki.`, path: svc.spacePath(space),
robots: open ? 'index, follow' : 'noindex, nofollow', cache: open ? 'public' : null,
jsonLd: open ? seo.structuredData.breadcrumbs([{ name: 'Wiki', url: `${config.baseUrl}/` }, { name: space.name, url: `${config.baseUrl}${svc.spacePath(space)}` }]) : null,
});
@@@ -173,8 +176,8 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
function editorsOnly(req, res, space) {
if (svc.access.canEdit(space, req.actor)) return true;
-- if (!req.actor.subject) needSignIn(req, res, 'Sign in to edit. Only editors of this space can change it.');
-- else errorPage(req, res, 403, 'Not an editor', 'Only editors of this space can do that. An owner of the space can give you a role.');
++ if (!req.actor.subject) needSignIn(req, res, 'Sign in to edit. Only editors of this wiki can change it.');
++ else errorPage(req, res, 403, 'Not an editor', 'Only editors of this wiki can do that. An owner of the wiki can give you a role.');
return false;
}
@@@ -214,7 +217,7 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
router.get('/s/:space/settings', wrap(async (req, res) => {
const space = await locateSpace(req, res);
if (!space) return;
-- if (!svc.access.canManage(space, req.actor)) return req.actor.subject ? errorPage(req, res, 403, 'Owners only', 'Only an owner of this space can change its settings.') : needSignIn(req, res, 'Sign in as an owner of this space.');
++ if (!svc.access.canManage(space, req.actor)) return req.actor.subject ? errorPage(req, res, 403, 'Owners only', 'Only an owner of this wiki can change its settings.') : needSignIn(req, res, 'Sign in as an owner of this wiki.');
send(req, res, 200, views.spaceSettingsPage({ space, roles: await svc.roles(space.id, req.actor), flash: req.query.saved ? 'Saved.' : null }), { title: `Settings of ${space.name}`, robots: 'noindex, nofollow' });
}));
@@@ -234,13 +237,13 @@ function createPages({ svc, viewers, platform, config, log = console, limits })
}
}));
-- // Import pages into a space (owners): the same JSON bundle as POST /api/v1/spaces/:space/import,
++ // Import pages into a wiki (owners): the same JSON bundle as POST /api/v1/spaces/:space/import,
// pasted into a form; the options come from the form's controls.
const importForm = express.urlencoded({ extended: false, limit: '6mb' });
function ownersOnly(req, res, space) {
if (svc.access.canManage(space, req.actor)) return true;
-- if (!req.actor.subject) needSignIn(req, res, 'Sign in as an owner of this space to import pages.');
-- else errorPage(req, res, 403, 'Owners only', 'Only an owner of this space imports pages into it.');
++ if (!req.actor.subject) needSignIn(req, res, 'Sign in as an owner of this wiki to import pages.');
++ else errorPage(req, res, 403, 'Owners only', 'Only an owner of this wiki imports pages into it.');
return false;
}
router.get('/s/:space/import', wrap(async (req, res) => {
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex aa465cd..b5f16c2 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -15,12 +15,12 @@ const { escapeHtml: esc } = require('openvibe-publishing/ssr');
const SITE_NAME = 'OpenVibe.Wiki';
const NETWORK_URL = 'https://openvibe.network';
--const DEFAULT_DESCRIPTION = 'OpenVibe.Wiki: wiki spaces with page trees, revision history, citations and discussion, part of the OpenVibe network.';
++const DEFAULT_DESCRIPTION = 'OpenVibe.Wiki: wikis with page trees, revision history, citations and discussion, part of the OpenVibe network.';
// The one AI summary for the site: /llms.txt and /llms-full.txt publish it, and the home page carries it
// as the shell's ai-summary meta / WebPage JSON-LD (openvibe-publishing/layout forwards `summary`).
--const AI_SUMMARY = 'Wiki spaces of the OpenVibe network: page trees, immutable revisions, citations attached to the revision that used them, infoboxes and internal links.';
++const AI_SUMMARY = 'Wikis of the OpenVibe network: page trees, immutable revisions, citations attached to the revision that used them, infoboxes and internal links.';
const NAV_LINKS = [
-- { label: 'Spaces', href: '/' },
++ { label: 'Wikis', href: '/' },
{ label: 'Recent changes', href: '/recent' },
{ label: 'Search', href: '/search' },
];
@@@ -49,7 +49,7 @@ function navConfig(o, config) {
service: 'wiki',
apiBase: NETWORK_URL,
links: [
-- { label: 'Spaces', href: '/', active: o.active === 'home' },
++ { label: 'Wikis', href: '/', active: o.active === 'home' },
{ label: 'Recent changes', href: '/recent', active: o.active === 'recent' },
{ label: 'Search', href: '/search', active: o.active === 'search' },
],
@@@ -84,7 +84,7 @@ function renderPage(o) {
siteName: SITE_NAME,
lang: 'en',
// The front page's title says what the site is (search results show it); every other page is "<page> · OpenVibe.Wiki".
-- title: o.title ? `${o.title} · ${SITE_NAME}` : o.path === '/' ? `${SITE_NAME}: wiki spaces with history and citations` : SITE_NAME,
++ title: o.title ? `${o.title} · ${SITE_NAME}` : o.path === '/' ? `${SITE_NAME}: wikis with history and citations` : SITE_NAME,
description: o.description || DEFAULT_DESCRIPTION,
summary: o.summary,
canonical: `${config.baseUrl}${o.path || '/'}`,
ddiff --git a/server/render/views.js b/server/render/views.js
iindex 3c26fdf..6f1b524 100644
---- a/server/render/views.js
++++ b/server/render/views.js
@@@ -11,11 +11,11 @@ const { infoboxToText } = require('../wiki/content');
const { html, raw } = ssr;
const t = (v) => raw(ssr.timeTag(v));
const e = encodeURIComponent;
--const VIS_LABEL = { public: 'Public', members: 'Members (any signed-in OpenVibe account)', vip: 'VIP (the space owner\'s OpenVibe.VIP members)', private: 'Private (people with a role in this space)' };
++const VIS_LABEL = { public: 'Public', members: 'Members (any signed-in OpenVibe account)', vip: 'VIP (the wiki owner\'s OpenVibe.VIP members)', private: 'Private (people with a role in this wiki)' };
function wpath(space, page) { return `/w/${e(space.slug)}/${e(page.slug)}`; }
--/** rel for outbound links (citations, infobox URLs): community spaces add ugc (see service.linkRel). */
++/** rel for outbound links (citations, infobox URLs): community wikis add ugc (see service.linkRel). */
function outRel(space) { return space && space.kind === 'official' ? 'noopener nofollow' : 'nofollow ugc noopener'; }
function notice(kind, text) { return html`<p class="wk-notice wk-${kind}" role="status">${text}</p>`; }
@@@ -26,7 +26,7 @@ function errorBody({ status, title, message }) {
return html`<section class="wk-error"><h1>${title}</h1><p>${message}</p><p class="wk-muted">HTTP ${String(status)}</p><p><a href="/">Back to the wiki</a></p></section>`;
}
--/** A VIP space or page the viewer is not (yet) admitted to: the join prompt (WS-K task 8). */
++/** A VIP wiki or page the viewer is not (yet) admitted to: the join prompt (WS-K task 8). */
function vipGateBody({ title, spaceName, joinUrl, signedIn, signInUrl }) {
return html`<section class="wk-error"><h1>For VIP members</h1><p>${title ? html`<strong>${title}</strong> is` : 'This is'} for the VIP members of ${spaceName ? html`<strong>${spaceName}</strong>'s owner` : 'its owner'}.</p>
<p>${signedIn ? '' : html`<a class="wk-btn" href="${signInUrl}">Sign in</a> `}${joinUrl ? html`<a class="wk-btn" href="${joinUrl}" rel="noopener">See their VIP plans</a>` : ''}</p><p><a href="/">Back to the wiki</a></p></section>`;
@@@ -48,13 +48,13 @@ function home({ spaces, recent, shelves = new Map(), actor }) {
const official = spaces.filter((s) => s.kind === 'official');
const user = spaces.filter((s) => s.kind === 'user');
const signedIn = !!(actor && actor.subject);
-- const start = signedIn ? { label: 'Start a space', href: '/new-space', primary: true } : { label: 'Sign in to start a space', href: '/auth/login?next=%2Fnew-space', primary: true };
++ const start = signedIn ? { label: 'Start a wiki', href: '/new-space', primary: true } : { label: 'Sign in to start a wiki', href: '/auth/login?next=%2Fnew-space', primary: true };
const card = (s) => {
const shelf = shelves.get(s.id);
return html`<li><a class="wk-space" href="/s/${e(s.slug)}"><b>${s.name}</b>${s.visibility !== 'public' ? html` <span class="wk-tag">${s.visibility}</span>` : ''}${s.description ? html`<span class="wk-space-text">${s.description}</span>` : ''}${shelf && shelf.total ? html`<span class="wk-space-meta">${String(shelf.total)} page${shelf.total === 1 ? '' : 's'}${shelf.updated ? html` · updated ${t(shelf.updated)}` : ''}</span>` : ''}</a></li>`;
};
-- // Each official space with pages: what is inside it, its pages one level down as cards with their summaries. A space
-- // without a shelf (not public, or empty) is a card under "Official spaces".
++ // Each official wiki with pages: what is inside it, its pages one level down as cards with their summaries. A space
++ // without a shelf (not public, or empty) is a card under "Official wikis".
const shelved = official.filter((s) => shelves.get(s.id) && shelves.get(s.id).pages.length);
const unshelved = official.filter((s) => !shelved.includes(s));
const inside = shelved.map((s) => {
@@@ -70,23 +70,23 @@ function home({ spaces, recent, shelves = new Map(), actor }) {
return html`${raw(showcase.hero({
eyebrow: 'OpenVibe.Wiki',
title: 'Knowledge,', accent: 'with sources',
-- lede: 'Wiki spaces with page trees, revision history, citations and discussion. Official spaces are edited by OpenVibe staff; anyone signed in with an OpenVibe account can start a space of their own.',
-- actions: [start, { label: 'Browse the spaces', href: '#spaces' }],
++ lede: 'Wikis with page trees, revision history, citations and discussion. Official wikis are edited by OpenVibe staff; anyone signed in with an OpenVibe account can start a wiki of their own.',
++ actions: [start, { label: 'Browse the wikis', href: '#spaces' }],
}))}
<div id="spaces"></div>
${raw(inside)}
--${unshelved.length || !shelved.length ? html`<section class="sc-sec wk-home-sec" aria-labelledby="wk-official"><h2 id="wk-official">Official spaces</h2>${unshelved.length ? html`<ul class="wk-spaces">${unshelved.map(card)}</ul>` : html`<p class="wk-muted">None yet.</p>`}</section>` : ''}
--<section class="sc-sec wk-home-sec" aria-labelledby="wk-community"><h2 id="wk-community">Community spaces</h2>${user.length ? html`<ul class="wk-spaces">${user.map(card)}</ul>`
-- : html`<div class="wk-empty"><p><b>No community spaces yet.</b> Any OpenVibe account can start one: public, members-only, for its owner's VIP members, or private, with the editors it chooses.</p><p><a class="wk-button" href="${start.href}">${start.label}</a></p></div>`}</section>
++${unshelved.length || !shelved.length ? html`<section class="sc-sec wk-home-sec" aria-labelledby="wk-official"><h2 id="wk-official">Official wikis</h2>${unshelved.length ? html`<ul class="wk-spaces">${unshelved.map(card)}</ul>` : html`<p class="wk-muted">None yet.</p>`}</section>` : ''}
++<section class="sc-sec wk-home-sec" aria-labelledby="wk-community"><h2 id="wk-community">Community wikis</h2>${user.length ? html`<ul class="wk-spaces">${user.map(card)}</ul>`
++ : html`<div class="wk-empty"><p><b>No community wikis yet.</b> Any OpenVibe account can start one: public, members-only, for its owner's VIP members, or private, with the editors it chooses.</p><p><a class="wk-button" href="${start.href}">${start.label}</a></p></div>`}</section>
${raw(showcase.features({
-- title: 'What a space gets',
++ title: 'What a wiki gets',
items: [
-- { icon: 'ov:wiki', title: 'Page trees', text: 'Pages nest under pages, so a space reads like a book.' },
++ { icon: 'ov:wiki', title: 'Page trees', text: 'Pages nest under pages, so a wiki reads like a book.' },
{ icon: 'ov:history', title: 'Revision history', text: 'Every published revision is kept; compare or restore any of them.' },
{ icon: 'ov:check', title: 'Citations', text: 'Claims carry their sources, listed with every page.' },
{ icon: 'ov:community', title: 'Discussion', text: 'Each page has a thread on OpenVibe.Community.' },
-- { icon: 'ov:account', title: 'Roles and visibility', text: 'Public, members-only, VIP or private spaces, with editors you choose.' },
-- { icon: 'ov:search', title: 'Search and feeds', text: 'Full-text search across the spaces you can see, and Atom and JSON feeds.' },
++ { icon: 'ov:account', title: 'Roles and visibility', text: 'Public, members-only, VIP or private wikis, with editors you choose.' },
++ { icon: 'ov:search', title: 'Search and feeds', text: 'Full-text search across the wikis you can see, and Atom and JSON feeds.' },
],
}))}
<section class="sc-sec wk-home-sec" aria-labelledby="wk-recent-h"><h2 id="wk-recent-h">Recently published</h2>${recent.length ? html`<ul class="wk-recent">${recent.map((r) => html`<li><span><a href="${wpath(r.space, r.page)}">${r.page.title}</a> <span class="wk-muted">in ${r.space.name}</span></span> <span class="wk-muted">${t(r.page.revision_published_at)}</span></li>`)}</ul>` : html`<p class="wk-muted">Nothing published yet.</p>`}
@@@ -101,12 +101,12 @@ function treeHtml(nodes, space) {
function spacePage({ space, tree, canEdit, canManage, proposals }) {
return html`${crumbs([{ name: 'Wiki', url: '/' }, { name: space.name }])}
<section><h1>${space.name}</h1>
--<p class="wk-muted">${space.kind === 'official' ? 'Official space' : 'Community space'} · ${VIS_LABEL[space.visibility]}</p>
++<p class="wk-muted">${space.kind === 'official' ? 'Official wiki' : 'Community wiki'} · ${VIS_LABEL[space.visibility]}</p>
${space.description ? html`<p>${space.description}</p>` : ''}
--${canEdit ? html`<p><a class="wk-button" href="/s/${e(space.slug)}/new">New page</a>${canManage ? html` <a href="/s/${e(space.slug)}/import">Import pages</a> <a href="/s/${e(space.slug)}/settings">Space settings</a>` : ''}</p>` : ''}
++${canEdit ? html`<p><a class="wk-button" href="/s/${e(space.slug)}/new">New page</a>${canManage ? html` <a href="/s/${e(space.slug)}/import">Import pages</a> <a href="/s/${e(space.slug)}/settings">Wiki settings</a>` : ''}</p>` : ''}
${proposals && proposals.length ? html`<p>${notice('info', `${proposals.length} AI proposal(s) wait for review.`)} <a href="/s/${e(space.slug)}/proposals">Review them</a></p>` : ''}
<h2>Pages</h2>${tree.length ? raw(treeHtml(tree, space)) : html`<p class="wk-muted">No pages yet.</p>`}
--${space.visibility === 'public' ? html`<p class="wk-muted">Follow this space: <a href="/s/${e(space.slug)}/feed.atom" type="application/atom+xml">Atom</a> · <a href="/s/${e(space.slug)}/feed.json" type="application/feed+json">JSON Feed</a></p>` : ''}
++${space.visibility === 'public' ? html`<p class="wk-muted">Follow this wiki: <a href="/s/${e(space.slug)}/feed.atom" type="application/atom+xml">Atom</a> · <a href="/s/${e(space.slug)}/feed.json" type="application/feed+json">JSON Feed</a></p>` : ''}
</section>`;
}
@@@ -190,7 +190,7 @@ function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolv
trail.push({ name: rev.fields.title || page.title });
const banners = [];
if (flash) banners.push(notice('ok', flash));
-- if (page.state !== 'published') banners.push(notice('warn', `This page is ${page.state === 'scheduled' ? 'scheduled, not published yet' : page.state}. Only editors of the space can see it.`));
++ if (page.state !== 'published') banners.push(notice('warn', `This page is ${page.state === 'scheduled' ? 'scheduled, not published yet' : page.state}. Only editors of the wiki can see it.`));
else if (!v.isPublishedRevision) banners.push(notice('warn', `You are looking at revision ${rev.number}, not the published revision ${page.published_revision}.`));
else if (v.canEdit && v.headNumber > rev.number) banners.push(html`<p class="wk-notice wk-info" role="status">Revision ${String(v.headNumber)} is newer than the published one and only editors can see it. <a href="${base}?rev=${String(v.headNumber)}">View it</a> · <a href="${base}/diff/${String(rev.number)}/${String(v.headNumber)}">compare</a></p>`);
if (v.disclosure) banners.push(html`<p class="wk-notice wk-ai" role="note"><strong>${v.disclosure.short}.</strong> ${v.disclosure.long}</p>`);
@@@ -292,15 +292,15 @@ function formValuesFromRevision(rev, page) {
}
function newSpacePage({ values = {}, error = null, staff = false }) {
-- return html`${crumbs([{ name: 'Wiki', url: '/' }, { name: 'New space' }])}
--<section><h1>Start a space</h1>${error ? notice('error', error) : ''}
++ return html`${crumbs([{ name: 'Wiki', url: '/' }, { name: 'New wiki' }])}
++<section><h1>Start a wiki</h1>${error ? notice('error', error) : ''}
<form method="post" action="/new-space" class="wk-form">
<label>Name <input type="text" name="name" required maxlength="120" value="${values.name || ''}"></label>
<label>Address (optional; lowercase letters, digits and dashes) <input type="text" name="slug" maxlength="63" pattern="[a-z0-9][a-z0-9-]{1,62}" value="${values.slug || ''}"></label>
<label>Description <input type="text" name="description" maxlength="1000" value="${values.description || ''}"></label>
<label>Visibility <select name="visibility">${['public', 'members', 'vip', 'private'].map((v) => html`<option value="${v}"${(values.visibility || 'public') === v ? raw(' selected') : ''}>${VIS_LABEL[v]}</option>`)}</select></label>
--${staff ? html`<label class="wk-check"><input type="checkbox" name="official" value="1"> Official (editorial) space</label>` : ''}
--<p><button type="submit">Create space</button></p></form></section>`;
++${staff ? html`<label class="wk-check"><input type="checkbox" name="official" value="1"> Official (editorial) wiki</label>` : ''}
++<p><button type="submit">Create wiki</button></p></form></section>`;
}
const IMPORT_EXAMPLE = JSON.stringify({ pages: [
@@@ -334,7 +334,7 @@ function spaceSettingsPage({ space, roles, error = null, flash = null }) {
<label>Name <input type="text" name="name" maxlength="120" value="${space.name}"></label>
<label>Description <input type="text" name="description" maxlength="1000" value="${space.description || ''}"></label>
<label>Visibility <select name="visibility">${['public', 'members', 'vip', 'private'].map((v) => html`<option value="${v}"${space.visibility === v ? raw(' selected') : ''}>${VIS_LABEL[v]}</option>`)}</select></label>
--<p class="wk-muted">Making a space members-only or private removes its pages from sitemaps, feeds and OpenVibe.Search.</p>
++<p class="wk-muted">Making a wiki members-only or private removes its pages from sitemaps, feeds and OpenVibe.Search.</p>
<p><button type="submit" name="op" value="settings">Save settings</button></p></form>
<h2>Roles</h2>
<table><thead><tr><th scope="col">Person</th><th scope="col">Role</th><th scope="col">Since</th><th scope="col"></th></tr></thead><tbody>
@@@ -345,7 +345,7 @@ ${roles.map((r) => html`<tr><td><code>${r.subject}</code></td><td>${r.role}</td>
<label>Role <select name="role"><option value="editor">editor</option><option value="viewer">viewer</option><option value="owner">owner</option></select></label>
<p><button type="submit">Grant role</button></p></form>
<h2>Import</h2><p><a href="/s/${e(space.slug)}/import">Import pages from a bundle</a></p>
--<h2>Delete</h2><form method="post" action="/s/${e(space.slug)}/settings" class="wk-form"><input type="hidden" name="op" value="delete"><label class="wk-check"><input type="checkbox" name="confirm" value="yes" required> Delete this space and take every page offline (addresses answer 410 Gone)</label><p><button type="submit">Delete space</button></p></form>
++<h2>Delete</h2><form method="post" action="/s/${e(space.slug)}/settings" class="wk-form"><input type="hidden" name="op" value="delete"><label class="wk-check"><input type="checkbox" name="confirm" value="yes" required> Delete this wiki and take every page offline (addresses answer 410 Gone)</label><p><button type="submit">Delete wiki</button></p></form>
</section>`;
}
@@@ -353,7 +353,7 @@ function pageSettingsPage({ space, page, v, parents, error = null, flash = null,
const base = wpath(space, page);
return html`${crumbs([{ name: 'Wiki', url: '/' }, { name: space.name, url: `/s/${e(space.slug)}` }, { name: page.title, url: base }, { name: 'Settings' }])}
<section><h1>Settings of ${page.title}</h1>${error ? notice('error', error) : ''}${flash ? notice('ok', flash) : ''}
--<p>State: <strong>${page.state}</strong>${page.published_revision ? html` · published revision ${String(page.published_revision)}` : ''} · visibility ${page.visibility} (space: ${space.visibility})</p>
++<p>State: <strong>${page.state}</strong>${page.published_revision ? html` · published revision ${String(page.published_revision)}` : ''} · visibility ${page.visibility} (wiki: ${space.visibility})</p>
<h2>Publish</h2>
<form method="post" action="${base}/settings" class="wk-form"><input type="hidden" name="op" value="publish">
<label>Revision <input type="number" name="revision" min="1" value="${String(v.headNumber)}"></label>
ddiff --git a/test/layout.test.js b/test/layout.test.js
iindex 3aba777..edd9d59 100644
---- a/test/layout.test.js
++++ b/test/layout.test.js
@@@ -52,7 +52,7 @@ const robotsOf = (head) => /<meta name="robots" content="([^"]*)">/.exec(head)[1
// The home page: indexable by an explicit robots string, plus the showcase kit's stylesheet.
r = await H.req(h, 'GET', '/');
let { head } = frameOf(r.text, '/');
-- assert.ok(head.includes('<title>OpenVibe.Wiki: wiki spaces with history and citations</title>'), `the home title says what the site is: ${(/<title>[^<]*<\/title>/.exec(head) || [''])[0]}`);
++ assert.ok(head.includes('<title>OpenVibe.Wiki: wikis with history and citations</title>'), `the home title says what the site is: ${(/<title>[^<]*<\/title>/.exec(head) || [''])[0]}`);
assert.ok(/<link rel="canonical" href="https?:\/\/[^"\/]+\/">/.test(head), 'the home canonical');
assert.strictEqual(robotsOf(head), 'index, follow');
assert.ok(/<link rel="stylesheet" href="\/shared\/[^"]*showcase[^"]*">/.test(head), 'the extra openvibe-shared stylesheet');
ddiff --git a/test/seed.test.js b/test/seed.test.js
iindex 37f72d4..ca57b49 100644
---- a/test/seed.test.js
++++ b/test/seed.test.js
@@@ -73,8 +73,8 @@ const data = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'seeds', 'ope
assert.strictEqual(shelfCards, data.pages.length - roots.length, 'one card for each page under the root');
assert.ok(shelf.includes('href="/w/openvibe/openvibe-live"'), 'cards link the pages');
assert.ok(!/<small>[^<]{241,}<\/small>/.test(shelf), 'a card carries one short sentence');
-- assert.ok(!homePage.text.includes('>Official spaces<'), 'a shelved space is not listed again');
-- assert.ok(homePage.text.includes('No community spaces yet.'), 'the community section says how to start one');
++ assert.ok(!homePage.text.includes('>Official wikis<'), 'a shelved space is not listed again');
++ assert.ok(homePage.text.includes('No community wikis yet.'), 'the community section says how to start one');
// Absent from sitemaps, feeds and Search until reviewed.
assert.strictEqual(((await H.req(h, 'GET', '/sitemaps/pages-1.xml')).text.match(/<loc>/g) || []).length, 0);
const atomFeed = await H.req(h, 'GET', '/feed.atom');
ddiff --git a/test/visibility.test.js b/test/visibility.test.js
iindex 446ad2f..6d6131c 100644
---- a/test/visibility.test.js
++++ b/test/visibility.test.js
@@@ -60,7 +60,7 @@ async function lastIndexEvent(h, pageId) {
assert.strictEqual(r.status, 200);
assert.match(r.headers.get('content-type'), /^text\/plain/);
assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=3600, stale-while-revalidate=3600');
-- assert.ok(r.text.startsWith('# OpenVibe.Wiki') && r.text.includes('> Wiki spaces of the OpenVibe network'), 'the llms.txt header comes first');
++ assert.ok(r.text.startsWith('# OpenVibe.Wiki') && r.text.includes('> Wikis of the OpenVibe network'), 'the llms.txt header comes first');
assert.ok(r.text.includes('http://wiki.test/w/notes/open-page') && r.text.includes('http://wiki.test/w/notes/second-page'));
assert.ok(!r.text.includes('unsourced-page'), 'only indexable pages, not merely listable ones');
assert.ok(Buffer.byteLength(r.text) <= 512 * 1024, 'the page stays within maxBytes');