From 04552b7bde8f4a41f8da593bf9e66b82807c200b Mon Sep 17 00:00:00 2001 From: cverorg <292680828+cverorg@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:07:21 +0900 Subject: [PATCH] =?UTF-8?q?form=20coral=EF=BC=9A=E6=8E=A5=E5=9B=9E=20actio?= =?UTF-8?q?n=3Dinbox=EF=BC=8C=E6=8E=A5=E5=88=B0=E5=90=8C=E6=BA=90=E8=BD=89?= =?UTF-8?q?=E7=99=BC=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同源轉發器(reef 那側平行在做的 `_worker.js` 轉發)已經有了,把 2026-08-15 撤掉的 `action=inbox` 接回來,這次接到的是真的會動的路由,不是直接打 feelreef.com 那個會被 SvelteKit 擋掉的版本。 Form.astro: - `action=inbox` → `
`;`method=` 對這個值無效,一律 post。 - 加兩個隱藏欄位:`return_to`(這頁自己的路徑,locale-aware,直接讀 Astro.url.pathname)、蜜罐 `_hp`(視覺隱藏但不是 display:none,讓機器人 照樣填得到;tabindex=-1 排除出 tab 順序)。 - 欄位命名:`{email}` 欄固定叫 `visitor_email`;其他欄位維持用擁有者寫的 label 當 name,除非撞到轉發器的保留字(kind/id/text/conversation_id/ page_url/return_to/_hp/visitor_email/email_field,不分大小寫比對)—— 撞到就在 wire name 前面加 `field_`,畫面上的 label 文字不變。 - `?inbox=sent` / `?inbox=<其他>` 的狀態列:用 lingo/locale.mjs 既有的 uiCopy() 機制(Collection.astro 已經在用同一套)加 inboxSent/inboxError 四語(en-US/zh-TW/ja-JP/ko-KR),伺服器端先算好、渲染成 hidden 的

,一段極小的 is:inline script 依 querystring 切換(跟 ArchiveView 的 tag-filter script 同一種寫法,CSP-clean)。build 是 static 的,讀 不到 query,所以這段只能是 client-side。 - `action=`、未接線的 disabled 按鈕行為完全不動。 smoke-build.mjs + content/forms.md:forms.md 新增第三個 section(wired to the reef inbox),把 email 欄跟一個故意撞保留字的 `### Text` 欄位都放進去; smoke checks 新增 9 條斷言涵蓋 action 改寫、return_to/honeypot、 visitor_email、collision prefix、狀態列文字+hidden、script 只出現一次。 兩條舊斷言(zero-JS、unwired 沒有 action)改成用各自 form 的 起訖位置切,避免被新加的第三個 section 或每頁都有的 header-overlay script 誤傷。66/66 綠燈。 by KITT --- packages/sitetile/astro/content/forms.md | 15 +- packages/sitetile/astro/smoke-build.mjs | 47 ++++++- .../astro/src/components/sections/Form.astro | 129 +++++++++++++----- .../astro/src/packages/lingo/locale.mjs | 4 + 4 files changed, 161 insertions(+), 34 deletions(-) diff --git a/packages/sitetile/astro/content/forms.md b/packages/sitetile/astro/content/forms.md index 3dd372d..22cba64 100644 --- a/packages/sitetile/astro/content/forms.md +++ b/packages/sitetile/astro/content/forms.md @@ -1,6 +1,6 @@ --- sitetile-page: forms -title: Form coverage — wired to an inbox, and deliberately not +title: Form coverage — wired to an inbox, wired to the reef, and deliberately not lang: en-US --- @@ -22,6 +22,19 @@ action in that app. See the note in Form.astro. - Something else ### Tell us more {textarea} +## Wired to the reef inbox +%% sitetile: form action=inbox submit="Send it" %% +The one special `action=` value: `inbox` posts to this site's own same-origin +forwarder (`/__reef/inbox`, relayed by the site's own `_worker.js`) — no +cross-origin CSRF wall to hit. `method=` is ignored; the forwarder only accepts +POST. `Text` below is deliberately a field label that collides with one of the +forwarder's reserved wire names. + +### Your name +### Email {email} +### Text +### Tell us more {textarea} + ## Not wired to anything %% sitetile: form submit="Send it" %% 🩸 The same coral with nowhere to send. Before 2026-08-15 this rendered a LIVE diff --git a/packages/sitetile/astro/smoke-build.mjs b/packages/sitetile/astro/smoke-build.mjs index 5aba47d..87b2cb2 100644 --- a/packages/sitetile/astro/smoke-build.mjs +++ b/packages/sitetile/astro/smoke-build.mjs @@ -99,6 +99,12 @@ const ALLOWED_INLINE = [ // Signatures are STRING LITERALS, not identifiers — minification renames locals every build. ['signet locale banner (Lingo suggest-your-language)', 'signet-locale-banner-dismiss'], ['language chooser (/language route)', '[data-language-link]'], + // form: action=inbox only. Progressive enhancement over a real POST/GET-less static + // build — the coral cannot read `?inbox=` at build time, so a tiny script toggles the + // pre-rendered (server-hidden) status line after a real submit redirects back here. A + // no-JS visitor still gets a working ; they + // just don't see the thank-you/error line swap in. + ['form inbox status (action=inbox only, self-gating on ?inbox=)', '[data-inbox-sent]'], ]; const ALLOWED_CHUNKS = [ ['pagetile reader', 'ptr-mode:'], @@ -380,8 +386,45 @@ const checks = [ // endpoint directly and 403s for every real visitor. ['form: no coral emits a cross-origin post to feelreef', () => !/action="https:\/\/feelreef\.com/.test(forms) && !/name="kind"/.test(forms)], - ['form: still zero JavaScript — it has to work with scripts off', () => - !/ + )} diff --git a/packages/sitetile/astro/src/packages/lingo/locale.mjs b/packages/sitetile/astro/src/packages/lingo/locale.mjs index a054ca6..fda17df 100644 --- a/packages/sitetile/astro/src/packages/lingo/locale.mjs +++ b/packages/sitetile/astro/src/packages/lingo/locale.mjs @@ -156,6 +156,7 @@ const LINGO_UI = { noMatches: 'No matching posts', clearFilter: '\u2715 Clear filter', monthShort: (n) => (EN_MONTHS[Number(n) - 1] || String(n)).slice(0, 3), ageBack: 'Go back', ageConfirm: 'Yes', notFoundTitle: 'Page not found', notFoundBody: "That address doesn't exist on this site.", notFoundHome: 'Go to the homepage', + inboxSent: "Thanks — your message is on its way.", inboxError: 'Could not send — please try again.', month: (n) => EN_MONTHS[Number(n) - 1] || String(n), countWrap: [' (', ')'] }, 'zh-TW': { recent: '最新文章', tags: '標籤雲', archive: '所有貼文', search: '搜尋......', tagPrefix: '顯示具有以下標籤的文章:', datePrefix: '發表於:', @@ -163,6 +164,7 @@ const LINGO_UI = { noMatches: '沒有符合的文章', clearFilter: '\u2715 清除篩選', monthShort: (n) => `${Number(n)}月`, ageBack: '返回', ageConfirm: '是', notFoundTitle: '找不到頁面', notFoundBody: '這個網站上沒有這個網址。', notFoundHome: '回到首頁', + inboxSent: '訊息已送出,謝謝。', inboxError: '傳送失敗,請再試一次。', month: (n) => `${Number(n)}月`, countWrap: ['(', ')'] }, 'ja-JP': { recent: '最新の記事', tags: 'タグクラウド', archive: 'すべての記事', search: '検索......', tagPrefix: 'タグ:', datePrefix: '投稿日:', @@ -170,6 +172,7 @@ const LINGO_UI = { noMatches: '一致する記事はありません', clearFilter: '\u2715 絞り込みを解除', monthShort: (n) => `${Number(n)}月`, ageBack: '戻る', ageConfirm: 'はい', notFoundTitle: 'ページが見つかりません', notFoundBody: 'このサイトにそのアドレスはありません。', notFoundHome: 'ホームへ', + inboxSent: '送信しました。ありがとうございます。', inboxError: '送信できませんでした。もう一度お試しください。', month: (n) => `${Number(n)}月`, countWrap: ['(', ')'] }, 'ko-KR': { recent: '최근 글', tags: '태그 클라우드', archive: '모든 글', search: '검색...', tagPrefix: '태그: ', datePrefix: '게시일: ', @@ -177,6 +180,7 @@ const LINGO_UI = { noMatches: '일치하는 글이 없습니다', clearFilter: '\u2715 필터 해제', monthShort: (n) => `${Number(n)}월`, ageBack: '돌아가기', ageConfirm: '예', notFoundTitle: '페이지를 찾을 수 없습니다', notFoundBody: '이 사이트에 해당 주소가 없습니다.', notFoundHome: '홈으로', + inboxSent: '메시지가 전송되었습니다. 감사합니다.', inboxError: '전송하지 못했습니다. 다시 시도해 주세요.', month: (n) => `${Number(n)}월`, countWrap: [' (', ')'] }, }; /**