Skip to content
Merged
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
2 changes: 1 addition & 1 deletion packages/cardtile/serve/card-worker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ function sandboxEditorHtml(localeKey, { host = '' } = {}) {
<html lang="${persona.lang}"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${escLite(persona.pageTitle)}</title>
<title>${escLite(host ? persona.hostPageTitle : persona.pageTitle)}</title>
<style>${EDIT2_CSS}</style>
</head>
<body>
Expand Down
3 changes: 3 additions & 0 deletions packages/cardtile/serve/card-worker.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,9 @@ test('host mode: /edit?host=feelreef serves the editor with host opts, noindex,
assert.deepEqual(optsOf(body), { host, sandbox: false, locale: 'ja', tableBase: '/try/edit/t/ja/' });
assert.ok(body.includes('id="sandbox-banner" hidden'), 'host mode ships the sandbox banner hidden');
assert.match(body, /id="host-save"/);
// 🔴 host mode never ships the sandbox's "Try Card · feelreef" — before the parent hands over
// a real card there is nothing to name yet, so the tab reads the bare product name.
assert.match(body, /<title>Card · feelreef<\/title>/, 'host mode ships the placeholder title, not the sandbox\'s');
assert.deepEqual(touched, []);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cardtile/serve/edit2-assets.mjs

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion packages/cardtile/w/sandbox-i18n.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

/**
* name → persona → { lang, name, bio, linkLabel, textBody, imageAlt, bannerStatus, bannerText,
* doorCta, reset, pageTitle }. `lang` is the BCP-47 tag written into the sandbox's `<html lang>`
* doorCta, reset, pageTitle, hostPageTitle }. `lang` is the BCP-47 tag written into the sandbox's `<html lang>`
* and the starter card's own frontmatter `lang:` field.
*/
export const SANDBOX_LOCALES = {
Expand All @@ -37,6 +37,7 @@ export const SANDBOX_LOCALES = {
doorCta: 'Make this card real',
reset: 'Start over',
pageTitle: 'Try Card · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: 'Next you\'ll sign in with your email (first time? your account is made along the way, no password). This card comes with you.',
doorContinue: 'Continue',
doorBack: 'Keep editing',
Expand All @@ -54,6 +55,7 @@ export const SANDBOX_LOCALES = {
doorCta: '把這張變成真的',
reset: '重新開始',
pageTitle: '試玩 Card · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: '接下來會請你用 email 登入(第一次會順便建好帳號,不用密碼)。這張卡會跟著你過去。',
doorContinue: '繼續',
doorBack: '再改一下',
Expand All @@ -71,6 +73,7 @@ export const SANDBOX_LOCALES = {
doorCta: 'これを本物にする',
reset: 'はじめからやり直す',
pageTitle: 'Card を試す · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: 'このあとメールアドレスでログインします(はじめての方はアカウントも一緒に作られます。パスワードは不要)。このカードはそのまま持っていけます。',
doorContinue: '続ける',
doorBack: 'もう少し直す',
Expand All @@ -88,6 +91,7 @@ export const SANDBOX_LOCALES = {
doorCta: '이 카드를 진짜로 만들기',
reset: '처음부터 다시',
pageTitle: 'Card 체험 · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: '다음에는 이메일로 로그인해요(처음이라면 계정도 함께 만들어져요. 비밀번호는 없어요). 이 카드는 그대로 가져갈 수 있어요.',
doorContinue: '계속하기',
doorBack: '조금 더 고치기',
Expand All @@ -106,6 +110,7 @@ export const SANDBOX_LOCALES = {
doorCta: '把这张卡片变成真的',
reset: '重新开始',
pageTitle: '试玩 Card · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: '接下来会请你用 email 登录(第一次会顺便建好账号,不用密码)。这张卡片会跟着你过去。',
doorContinue: '继续',
doorBack: '再改一下',
Expand All @@ -123,6 +128,7 @@ export const SANDBOX_LOCALES = {
doorCta: 'Diese Karte echt machen',
reset: 'Von vorn beginnen',
pageTitle: 'Card ausprobieren · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: 'Als Nächstes melden Sie sich mit Ihrer E-Mail-Adresse an (beim ersten Mal wird Ihr Konto gleich mit angelegt, ohne Passwort). Diese Karte kommt mit.',
doorContinue: 'Weiter',
doorBack: 'Weiter bearbeiten',
Expand All @@ -140,6 +146,7 @@ export const SANDBOX_LOCALES = {
doorCta: 'Rendre cette carte réelle',
reset: 'Recommencer',
pageTitle: 'Essayer Card · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: 'Ensuite, vous vous connecterez avec votre e-mail (la première fois, votre compte est créé au passage, sans mot de passe). Cette carte vous suit.',
doorContinue: 'Continuer',
doorBack: 'Continuer à modifier',
Expand All @@ -157,6 +164,7 @@ export const SANDBOX_LOCALES = {
doorCta: 'Hacer real esta tarjeta',
reset: 'Empezar de nuevo',
pageTitle: 'Probar Card · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: 'A continuación iniciarás sesión con tu correo (la primera vez tu cuenta se crea por el camino, sin contraseña). Esta tarjeta se va contigo.',
doorContinue: 'Continuar',
doorBack: 'Seguir editando',
Expand All @@ -174,6 +182,7 @@ export const SANDBOX_LOCALES = {
doorCta: 'Tornar este cartão real',
reset: 'Começar de novo',
pageTitle: 'Experimentar Card · feelreef',
hostPageTitle: 'Card · feelreef',
doorNote: 'Em seguida você entra com o seu e-mail (na primeira vez a conta é criada junto, sem senha). Este cartão vai com você.',
doorContinue: 'Continuar',
doorBack: 'Continuar editando',
Expand Down
2 changes: 1 addition & 1 deletion packages/cardtile/w/sandbox-i18n.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test('SANDBOX_LOCALES: every one of the nine has a complete persona (no missing
// the door: its button, the sentence before it, and the two ways out of that sentence. A locale
// missing any of these is a locale where the door opens onto silence — see bootSandbox().
'doorCta', 'doorNote', 'doorContinue', 'doorBack',
'reset', 'pageTitle', 'coachMark'];
'reset', 'pageTitle', 'hostPageTitle', 'coachMark'];
for (const key of NINE) {
const p = SANDBOX_LOCALES[key];
assert.ok(p, `${key}: missing from SANDBOX_LOCALES entirely`);
Expand Down
21 changes: 21 additions & 0 deletions packages/cardtile/w2/edit2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1280,8 +1280,28 @@ function hostSaveKey(doc) {
}, true);
}

/** the card's own name — the frontmatter `title:` line `setTitle` writes — or '' if there is none.
* Read-only sibling of `setTitle`'s fence-scoped parse: never reads a `title:`-looking sentence
* from an author's lead prose, only the frontmatter fence itself. */
function cardTitleFromMd(md) {
const pre = (() => { try { return parseCard(md).pre || ''; } catch { return ''; } })();
const fence = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*$/m.exec(pre);
const m = fence && /^title:\s*(.*)$/m.exec(fence[1]);
return m ? m[1].trim() : '';
}

/** host mode's `<title>` — "Card · feelreef" until a real card lands, then that card's own name
* ahead of it. The sandbox keeps its own localised "Try Card · feelreef" (set server-side, in
* card-worker.mjs's `sandboxEditorHtml`, and never touched here). */
function paintHostTitle(cardTitle) {
if (typeof document === 'undefined') return;
const brand = SANDBOX_LOCALES[primaryLocale(S.locale)].hostPageTitle;
document.title = cardTitle ? `${cardTitle} · ${brand}` : brand;
}

function bootHost() {
document.body.dataset.hostWaiting = '1'; // nothing to edit until the parent hands a card over
paintHostTitle(''); // same placeholder the server already rendered
const saveBtn = el('host-save');
saveBtn.textContent = T.pubSave;
saveBtn.hidden = false;
Expand All @@ -1295,6 +1315,7 @@ function bootHost() {
S.undo = [];
delete document.body.dataset.hostWaiting;
commit(md, { undoable: false });
paintHostTitle(cardTitleFromMd(md) || S.handle || '');
},
});
saveBtn.onclick = () => S.bridge.save();
Expand Down
19 changes: 18 additions & 1 deletion packages/cardtile/w2/host-boot.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function bootHost() {
ctx.window.__cardtileW2Boot({ host: HOST, sandbox: false, locale: 'en', tableBase: '/try/edit/t/en/' });
const runTimers = () => { const due = timers.splice(0); for (const t of due) if (t.fn) t.fn(); };
const message = (data, origin = HOST) => { for (const fn of listeners.message || []) fn({ origin, data }); };
return { get, body, posted, runTimers, message, timers };
return { get, body, document, posted, runTimers, message, timers };
}

test('🔴 host mode with no card:load: the canvas never receives the seed, and the page says it is waiting', () => {
Expand Down Expand Up @@ -90,6 +90,23 @@ test('host mode: card:load renders THE card, and the ready announcements stop',
assert.ok(h.posted.length >= before);
});

// 🔴 the bug this pair guards: before card:load lands, `/edit?host=…` used to carry the SANDBOX's
// title ("Try Card · feelreef") even while framed in the owner's dashboard editing a real card —
// see card-worker.mjs's sandboxEditorHtml() and the `hostPageTitle` it now renders instead.
test('host mode: document.title is the bare placeholder before card:load, the card\'s own name after', () => {
const h = bootHost();
assert.equal(h.document.title, 'Card · feelreef');
h.message({ type: 'card:load', v: 1, md: REAL, handle: 'mei', cardUrl: 'https://card.feelreef.com/mei' });
assert.equal(h.document.title, '小美 · Card · feelreef');
});

test('host mode: document.title falls back to the handle when the loaded card carries no frontmatter title', () => {
const h = bootHost();
const noTitle = ['## cards', '', '- [ ] %% card: profile w=6 %% a real bio'].join('\n') + '\n';
h.message({ type: 'card:load', v: 1, md: noTitle, handle: 'mei', cardUrl: 'https://card.feelreef.com/mei' });
assert.equal(h.document.title, 'mei · Card · feelreef');
});

test('CONTROL: the stub does record a painted seed (loaded through card:load)', () => {
// without this, "no srcdoc" above could mean the stub never records a paint at all
const h = bootHost();
Expand Down
Loading