From 0d517b8bec2afae0dfa846f9ad95d1fac36789bd Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Wed, 26 Aug 2026 20:14:36 -0400 Subject: [PATCH 1/2] feat(about): signpost the "Is this Shariah law?" answer where a skeptic will find it (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #90. A Catholic friend of the maintainer opened a message with, in substance, "you prefer sharia, I oppose it" — friendly, well-disposed, and gone before anything explained what keel is. He is a representative reader, not an outlier. The site already wins this argument. `compliance.ts` → `framing` is titled "Is this 'Shariah law'?" and is good: it reframes the word doing the damage (*law* implies something imposed on others, while keel constrains one operator's own trading), it says no ruling is embedded in the software because the ruling is an input, and it notes the usury prohibition ran through Aquinas and the Third Lateran Council for a thousand years — "not a foreign import; a road not taken." That argument sits four scroll-lengths into a methodology page, behind a nav item a skeptical reader has no reason to open, after he has already decided. **This is placement, not content.** Nothing new is argued here. ### What changed - `CompliancePage.astro` — the framing section gains `id="is-this-shariah-law"` so it can be deep-linked, matching `id="from-source"` / `id="open-source"`. - `about.ts` — one FAQ item, all three locales, four sentences, linking to that anchor. Every substantive clause is a restatement of copy already shipped in `framing`. - `FaqItem` gains an optional `link {label, page, hash}`, and `AboutPage.astro` builds the href with `localePath`. Answers render as escaped plain text, and this repo never writes internal hrefs into copy files — `community.ts`, `compliance.ts` and `features.ts` all use structured link fields. Following that rather than injecting HTML or hardcoding three locale paths. ### Placed third, not first After the two existing "Is this…?" disambiguations, which are a matched pair about search-result collisions. That gives a run of three parallel questions ascending in importance, above the two longest items, so a scanner reaches it. Deliberately not first: leading the About FAQ with the objection reads as defensive, and prioritizes the skeptic over the audience keel is actually built for. #90 raises that trade-off; this is the answer to it. ### What is NOT claimed Not that keel is denomination-agnostic, works for any faith, or that another rule pack "would run on the same rails". That is **false today** — `keel/compliance/screen.py:33` is a hardcoded `frozenset` matched at `:334` with no config override anywhere. What is true, and all this says, is what the compliance page already said: the *ruling* is an operator input, so two operators of different schools get different results from identical code. The sector taxonomy is fixed in code. Rulings are pluggable; the vocabulary is not. A capability promise is the worst FR-9 violation because it is the hardest to walk back. Swept the diff for `denomination|any faith|catholic|usccb|agnostic|pack| configur` — zero hits in all three locales. ### Two stale comments, corrected `astro.config.mjs:7` and `src/i18n/config.ts:5` both still said "`fr` and `es` are reserved in routing but ship no pages until their phase." Wrong in both directions: `fr` ships the full page set, and `es` is not in `locales` at all. The second was found while fixing the first — same sentence, same defect, so both go together rather than leaving a known-false comment behind. ### Verification - `npm run check` — 0 errors, 0 warnings - `npm run build` — 56 pages, complete - Anchor renders in `dist/{en,ar,fr}/compliance/`; the FAQ link resolves to `/{locale}/compliance/#is-this-shariah-law` in all three - Zero `stale-banner` occurrences on any About page; all five FR-8 markers at `2026-08-26.1` Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015cBJ2syTN4VoE73F9sBNGW --- astro.config.mjs | 3 +- src/components/pages/AboutPage.astro | 14 +++++++ src/components/pages/CompliancePage.astro | 2 +- src/i18n/config.ts | 6 +-- src/i18n/pages/about.ts | 47 ++++++++++++++++++++--- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 199f812..251f51f 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -4,7 +4,8 @@ import sitemap from "@astrojs/sitemap"; import { rehypeTableHeaders } from "./src/plugins/rehype-table-headers.mjs"; // D4: i18n in the architecture from day one, phased in content. -// `fr` and `es` are reserved in routing but ship no pages until their phase. +// `en`, `ar`, and `fr` are all routed and all ship the full page set; `es` is +// not a locale yet — adding one stays content + config only. // D7 / FR-1: static-first — no adapter, zero client JS by default. export default defineConfig({ site: "https://keeltrading.com", diff --git a/src/components/pages/AboutPage.astro b/src/components/pages/AboutPage.astro index 2f10d38..9bb9ee6 100644 --- a/src/components/pages/AboutPage.astro +++ b/src/components/pages/AboutPage.astro @@ -40,6 +40,13 @@ const c = about[locale]; {item.answer.map((paragraph) => (

{paragraph}

))} + {item.link ? ( +

+ + {item.link.label} + +

+ ) : null} )) } @@ -54,6 +61,13 @@ const c = about[locale]; {item.answer.map((paragraph) => (

{paragraph}

))} + {item.link ? ( +

+ + {item.link.label} + +

+ ) : null} )) } diff --git a/src/components/pages/CompliancePage.astro b/src/components/pages/CompliancePage.astro index 27b1ad4..490df4b 100644 --- a/src/components/pages/CompliancePage.astro +++ b/src/components/pages/CompliancePage.astro @@ -72,7 +72,7 @@ const honestLinks = [ -
+

{c.framing.title}

{c.framing.body.map((paragraph) =>

{paragraph}

)}
diff --git a/src/i18n/config.ts b/src/i18n/config.ts index f17de2a..29ebd24 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,9 +1,9 @@ /** * FR-7 — i18n & RTL in the architecture from day one. * - * `en` is the source of truth; `ar` is the launch translation. `fr`/`es` are - * reserved in routing (astro.config.mjs) but ship no pages until their phase - * (D4) — adding a locale must be content + config only. + * `en` is the source of truth; `ar` was the launch translation and `fr` has + * since shipped the full page set. `es` is not a locale yet (D4) — adding one + * must stay content + config only. */ export const locales = ["en", "ar", "fr"] as const; export type Locale = (typeof locales)[number]; diff --git a/src/i18n/pages/about.ts b/src/i18n/pages/about.ts index 2f8b6ed..2e19ffd 100644 --- a/src/i18n/pages/about.ts +++ b/src/i18n/pages/about.ts @@ -1,4 +1,4 @@ -import type { LocalizedPage } from "../config"; +import type { LocalizedPage, PageKey } from "../config"; /** * About (FR-2): license, trademarks, brand FAQ including the keel.sh @@ -7,6 +7,8 @@ import type { LocalizedPage } from "../config"; export interface FaqItem { question: string; answer: string[]; + /** Optional deep link after the answer; AboutPage builds the path with localePath. */ + link?: { label: string; page: PageKey; hash?: string }; } export interface AboutContent { @@ -24,7 +26,7 @@ export interface AboutContent { export const about: LocalizedPage = { en: { - rev: "2026-08-20.2", + rev: "2026-08-26.1", title: "About keel — Name, License, keel.sh & KEEL FAQ", description: "Who runs keeltrading.com, licensing, and the brand FAQ — why this keel is not Kubernetes Keel (keel.sh) nor Keel Infrastructure (KEEL).", @@ -53,6 +55,17 @@ export const about: LocalizedPage = { "No. A Nasdaq-listed bitcoin-mining company (formerly Bitfarms) trades under the ticker KEEL, so a search for “keel trading” or “keel crypto” may show its stock chart near these results. It has no connection to this project. This keel is an open-source compliance engine by CodeGate Software — if you landed on keeltrading.com, you are in the right place.", ], }, + { + question: "Is this “Shariah law”?", + answer: [ + "No. keel constrains its operator's own trading and governs nobody else's conduct. No ruling is embedded in the software: the ruling is an input the operator records, not an output the engine produces. The honest description is a compliance engine that happens to have been built for one ethical framework.", + ], + link: { + label: "The full argument, on the compliance page", + page: "compliance", + hash: "#is-this-shariah-law", + }, + }, { question: "Why is it called keel?", answer: [ @@ -107,8 +120,8 @@ export const about: LocalizedPage = { }, ar: { - rev: "2026-08-20.2", - translatedFromRev: "2026-08-20.2", + rev: "2026-08-26.1", + translatedFromRev: "2026-08-26.1", title: "حول كيل — الاسم والترخيص وأسئلة keel.sh وKEEL", description: "مَن يُشغّل keeltrading.com، والتراخيص، والأسئلة الشائعة عن الاسم — ولماذا ليس هذا كيلَ Kubernetes‏ (keel.sh) ولا شركةَ Keel Infrastructure‏ (KEEL).", @@ -137,6 +150,17 @@ export const about: LocalizedPage = { "لا. فثمّة شركةُ تعدين بيتكوين مدرجةٌ في ناسداك (كانت تُعرف سابقًا بـ Bitfarms) تتداول تحت الرمز KEEL، وقد يُظهر البحث عن «keel trading» أو «keel crypto» مخطّطَ سهمها قرب هذه النتائج. ولا صلة لها بهذا المشروع. فكيل هذا محرّكُ امتثالٍ مفتوح المصدر من CodeGate Software — وإن كنت قد وصلت إلى keeltrading.com فأنت في المكان الصحيح.", ], }, + { + question: "أهذا «القانون الشريعي»؟", + answer: [ + "لا. فكيل يقيّد تداول مشغّله وحده، ولا يحكم سلوكَ أحدٍ غيره. ولا حُكمَ مضمّنًا في البرمجية: فالحُكم مُدخَلٌ يسجّله المشغّل، لا مخرجٌ ينتجه المحرّك. والوصفُ الصادق: محرّكُ امتثالٍ صُدِف أن بُني لإطارٍ أخلاقيٍّ واحد.", + ], + link: { + label: "الحجّة كاملةً في صفحة الامتثال", + page: "compliance", + hash: "#is-this-shariah-law", + }, + }, { question: "لماذا سُمّي المشروع «كيل»؟", answer: [ @@ -191,8 +215,8 @@ export const about: LocalizedPage = { }, fr: { - rev: "2026-08-20.2", - translatedFromRev: "2026-08-20.2", + rev: "2026-08-26.1", + translatedFromRev: "2026-08-26.1", title: "À propos de keel — nom, licence et FAQ keel.sh & KEEL", description: "Qui édite keeltrading.com, les licences, et la FAQ de la marque — pourquoi ce keel n'est ni le Keel de Kubernetes (keel.sh) ni Keel Infrastructure (KEEL).", @@ -221,6 +245,17 @@ export const about: LocalizedPage = { "Non. Une société de minage de bitcoin cotée au Nasdaq (ex-Bitfarms) est cotée sous le symbole KEEL : une recherche sur « keel trading » ou « keel crypto » peut donc faire remonter son cours de Bourse. Elle n'a aucun lien avec ce projet. Ce keel-ci est un moteur de conformité open-source signé CodeGate Software — si vous êtes arrivé sur keeltrading.com, vous êtes au bon endroit.", ], }, + { + question: "Est-ce « la loi shariah » ?", + answer: [ + "Non. keel contraint le trading de son opérateur, et ne gouverne la conduite de personne d'autre. Aucun jugement n'est encodé dans le logiciel : le jugement est une entrée que l'opérateur enregistre, pas une sortie que le moteur produit. La description honnête : un moteur de conformité qu'il est advenu qu'on bâtisse pour un cadre éthique précis.", + ], + link: { + label: "L'argument complet, sur la page Conformité", + page: "compliance", + hash: "#is-this-shariah-law", + }, + }, { question: "Pourquoi ce nom, keel ?", answer: [ From 6fbe1c899369a3ee446848529ccb4ad941999f0d Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Wed, 26 Aug 2026 20:19:16 -0400 Subject: [PATCH 2/2] refactor(about): type the FAQ hash as a fragment, not a bare string (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review nit, taken because it costs nothing and closes a silent failure. `hash?: string` accepted "is-this-shariah-law" without the leading `#`, which would have produced `/en/compliance/is-this-shariah-law` — a 404 that type-checks. `hash?: \`#\${string}\`` makes the compiler reject it. One call site today, so this is future-proofing rather than a fix. `npm run check` — 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015cBJ2syTN4VoE73F9sBNGW --- src/i18n/pages/about.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/pages/about.ts b/src/i18n/pages/about.ts index 2e19ffd..156ce89 100644 --- a/src/i18n/pages/about.ts +++ b/src/i18n/pages/about.ts @@ -8,7 +8,7 @@ export interface FaqItem { question: string; answer: string[]; /** Optional deep link after the answer; AboutPage builds the path with localePath. */ - link?: { label: string; page: PageKey; hash?: string }; + link?: { label: string; page: PageKey; hash?: `#${string}` }; } export interface AboutContent {