diff --git a/.gitflow b/.gitflow
new file mode 100644
index 0000000..472221e
--- /dev/null
+++ b/.gitflow
@@ -0,0 +1,9 @@
+[gitflow]
+ branch.master = main
+ branch.develop = develop
+ prefix.feature = feature/
+ prefix.bugfix = bugfix/
+ prefix.release = release/
+ prefix.hotfix = hotfix/
+ prefix.support = support/
+ versiontag = v
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 407565e..e405d06 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,7 +2,7 @@ name: CI
on:
push:
- branches: [main]
+ branches: [main, develop]
pull_request:
jobs:
diff --git a/.husky/pre-push b/.husky/pre-push
new file mode 100644
index 0000000..276f93c
--- /dev/null
+++ b/.husky/pre-push
@@ -0,0 +1,20 @@
+# push-sec: block direct/forced pushes to protected branches (Git Flow).
+# main + develop are protected: code reaches them only via PR merges
+# (and main additionally from release/hotfix merges). Force pushes are
+# blocked everywhere. Safe, single-owner escape hatch: ALLOW_PROTECTED_PUSH=1.
+if [ "$ALLOW_PROTECTED_PUSH" = "1" ]; then
+ exit 0
+fi
+protected="refs/heads/main refs/heads/develop"
+while read -r local_ref local_oid remote_ref remote_oid; do
+ if [ "$local_ref" = "+${local_ref#+}" ]; then
+ echo "push-sec: force push blocked ($local_ref). Protected branches never allow force." >&2
+ exit 1
+ fi
+ case " $protected " in
+ *" $remote_ref "*)
+ echo "push-sec: direct push to $remote_ref blocked. Open a pull request; $remote_ref only receives PR/release merges." >&2
+ exit 1
+ ;;
+ esac
+done
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index b67403c..e32fa9d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,12 +35,12 @@ day one.
`recovery.ts` (12-word BIP39 secret + salted verifier), `transfer.ts`/`lan.ts` (QR/LAN), content
loaders (`quran.ts`, `tafsir.ts`, `audio.ts`, `hadith-full.ts`),
`privacy.ts` (sensitivity + network manifest), `strings.ts` (AR/EN
- dictionary, ~1295 keys), `i18n.ts`.
+ dictionary, ~1307 keys), `i18n.ts`.
- `app/components/` — React UI. `app/components/views/` route views,
`app/components/library/` Quran/hadith, `transfer.tsx` (export infra),
`profile-scope.tsx`, `login-gate.tsx`.
- `app/` — routes: `/`(today), `/account`, `/calendar`, `/insights`,
- `/review`, `/deen`, `/library`, `/recovery`.
+ `/deen`, `/library`, `/recovery`.
- `e2e/` — Playwright (serial; `npm run test:e2e` pins `--workers=1`).
- `public/data/` — generated bundles (only touched via pipeline).
- `docs/`, `DOCUMENTATION.md`, `SECURITY.md`, `GOVERNANCE.md` — ground
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54ef6db..a71e998 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,33 @@ or architecture-level changes are listed.
## Unreleased
+- **الحصاد folded into /deen**: the previous `/review` night-checklist page
+ now lives as a third "المراجعة / Review" tab inside the Deen journey
+ (`/deen`, tabs: Today · Library · Review); `/review` permanently
+ redirects (`308`→`/deen`) so old bookmarks, shared links, and PWA
+ notification deep-links keep working; nav, sitemap, SW `CORE` (bumped
+ to `wird-v7`), manifest shortcuts, notification defaults, metadata,
+ SEO/docs/audit counts, and all static + e2e suites updated.
+- Branch security: GitHub-server protection ENABLED on `main` + `develop`
+ (`gh` API 2026-09-23): require PR + `CI / verify` status check (strict),
+ linear history, no force pushes, no deletions, no admins bypass —
+ direct pushes fail server-side (cryptographic signature check left off
+ until GPG/SSH signing is configured; `git commit -s` sign-off stays a
+ rule); local `.husky/pre-push` remains as the client-side wall;
+ `branch-guard` test keeps both documented.
+- SEO content pass: homepage FAQ (5 honest long-tail Q&A) + matching
+ `FAQPage` JSON-LD, descriptive internal links to core routes, and new
+ regression guards (unique titles/descriptions per route, FAQ parity);
+ `docs/INDEXING.md` gained the post-index playbook (keyword/CTR/Vitals
+ owner steps).
- Public-launch ops: `docs/INDEXING.md` records the verified live-index
state (robots/sitemap/verification file/home metadata all `200`) and
the Search Console checklist that actually puts Wird in Google;
- `docs/BRANCHING.md` introduces GitHub Flow (protected `main`,
- `feat/fix/docs/chore/hotfix` PR branches, signed commits).
+ **branch system live**: Git Flow with long-lived `main` + `develop`
+ (both on origin), `feature/bugfix/release/hotfix/support/*` short-lived
+ branches, `.gitflow` config, CI now also gates `develop`,
+ `docs/BRANCHING.md` documents it all (protection of both branches is an
+ owner action — `gh`/PAT not available from the dev env).
- Polish + presence: README screenshots generated from the app
(`docs/assets/shots/`, via `scripts/shot-readme.mjs`); raster app
icons (180/192/512 PNGs derived 1:1 from `icon.svg` via
diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md
index 33c3810..b58aab1 100644
--- a/DOCUMENTATION.md
+++ b/DOCUMENTATION.md
@@ -100,8 +100,7 @@ husky pre-commit (lint-staged) + commit-msg hooks.
| `/` | `app/page.tsx` | Today: hero, habits, rescue plan, goals, night review |
| `/calendar` | `app/calendar/` | 30-day month grid, Friday plan |
| `/insights` | `app/insights/` | Real stats, balance radar, coach brief, Hijri year |
-| `/review` | `app/review/` | End-of-day checklist (tri-state) + score + mood |
-| `/deen` | `app/deen/` | Deen journey: levels, quests, deeds, reflections, streaks (see §3) |
+| `/deen` | `app/deen/` | Deen journey — tabs: Today · Library · Review (see §3) |
| `/library` | `app/library/` | Tabs: Adhkar · Quran · Hadith · Paths · Dreams |
| `/account` | `app/account/` | Profile, backup, transfer, reminders, times, theme |
| `/recovery` | `app/recovery/` | Last-resort recovery: inspect, emergency export, restore, surgical reset |
@@ -114,6 +113,10 @@ Shared chrome (sidebar, bottom nav, header, zikr dock, login gate) lives in
(`WirdProvider` + `useWird()`). Views that need props live in
`app/components/views/`.
+> `/review` was folded into `/deen` (Review tab) and now permanently
+> redirects — old bookmarks, shared links, and PWA notification deep-links
+> keep working.
+
## 3. Features by route
**Today (`/`)** — hero ring (live %), day-mode pills, focus card, quick tools,
@@ -126,10 +129,11 @@ reflection (persisted textarea), tiered return screen after ≥3 absent days
adaptive companion card (one ranked guidance + verified verse/hadith, see
§14), kids quests, qada / fasting / breaker cards, Ramadan banner in Ramadan.
-**Review (`/review`)** — auto-built checklist from today's real data plus six
-heart-check items; tri-state (done/partial/missed); mood + gratitude; weighted
-score saved immutably per day (`wird-reviews-v1`, merged into history);
-one-line night context for strong/low/return days (see §14).
+**Review (Deen journey, `/deen` → Review tab)** — auto-built checklist from
+today's real data plus six heart-check items; tri-state (done/partial/missed);
+mood + gratitude; weighted score saved immutably per day (`wird-reviews-v1`,
+merged into history); one-line night context for strong/low/return days
+(see §14). Retains tab state in memory only, like the other deen tabs.
**Insights (`/insights`)** — period pills (1–365d buckets), weekly bars, 4
metrics, coach brief (at-risk → neglect → pace → lift + praise), 6-axis
diff --git a/MASTER_AUDIT.md b/MASTER_AUDIT.md
index a60890d..6de3c29 100644
--- a/MASTER_AUDIT.md
+++ b/MASTER_AUDIT.md
@@ -30,8 +30,8 @@ the JSONs). This run's numbers: `AUDIT_REPORT.md`. Frozen snapshot:
## 5–7. Routes, journeys, navigation — PASS (+1 FIXED)
-- Inventory: 10 routes (`/`, account, calendar, insights, library,
- recovery, review, private-plans, terms, privacy); static check
+- Inventory: 10 routes (`/`, account, calendar, insights, deen, library,
+ recovery, private-plans, terms, privacy); static check
`app/lib/__tests__/audit.test.ts` asserts every `href="/…"` and
`router.push("/…")` resolves, sitemap lists the public routes, and the
SW `CORE` shell matches existing routes (a stale shell entry would
diff --git a/README.md b/README.md
index 472b556..25b56d0 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ Node 22 (`.nvmrc`). CI (`.github/workflows/ci.yml`) runs typecheck → lint →
- **اليوم**: صلوات وأوراد، خطة إنقاذ، جلسة قرآن بمؤقت، بطاقات الجمعة/الوتر، عادات مخصصة، تحديات، عهود، ركن الصغار، قضاء الفوائت، الصيام، كسر العادات، وضع رمضان التلقائي.
- **المرشد التكيفي**: بطاقة واحدة ذكية (26 حالة: عودة، تعثر، زخم، إرهاق…) مع آية/حديث موثّقين من مجموعات محلية، ومسار توبة اختياري بعد الغياب الطويل — دون أحكام ودون ادعاء معرفة القلوب.
-- **الحصاد** (`/review`): مراجعة ليلية tri-state مع درجة ومزاج وامتنان — تُحفظ كسجل يومي، مع سطر سياق ليلي.
+- **الدين** (`/deen`): رحلة اليوم (أذكار ومهام وأعمال صالحة) + مكتبة وتعلم + مراجعة ليلية tri-state مع درجة ومزاج وامتنان — تُحفظ كسجل يومي، مع سطر سياق ليلي.
- **التقدّم** (`/insights`): طبقات تحليلية محلية قابلة للتفسير (اتجاهات، عبادة-عبادة، قرآن وحفظ، أذكار، أهداف وتحديات وعهود، عودات واستمرارية، خريطة نشاط، مراجعة شهرية/سنوية، إنجازات) + مدى مخصص للمقارنة — لا بيانات مفتعلة أبدًا.
- **المكتبة** (`/library`): قارئ قرآن كامل دون إنترنت (بحث، علامات، حفظ بتاريخ مراجعة)، مختارات الكتب التسعة + الأربعون النووية، مسارات علمية ٤ مستويات × ٨ علوم، لوحة الأحلام.
- **العودة**: شاشة رجوع متدرجة حسب عمق الغياب (3/7/14/30/90 يومًا) + سلّم تنبيهات محلية + مواقيت يدوية بعدّادات حية + وضع المسجد.
diff --git a/app/additions.css b/app/additions.css
index 86c0e8a..8692703 100644
--- a/app/additions.css
+++ b/app/additions.css
@@ -2245,3 +2245,64 @@ video {
animation: none;
}
}
+
+/* SEO FAQ + internal links (public landing block at page bottom). */
+.seo-faq {
+ margin-block: 40px 8px;
+ border-block-start: 1px solid var(--border, #d8dfd5);
+ padding-block-start: 16px;
+ max-inline-size: 720px;
+ margin-inline: auto;
+}
+
+.seo-faq h2,
+.seo-links h3 {
+ font-size: 17px;
+ margin: 0 0 10px;
+}
+
+.seo-faq details {
+ border-block-end: 1px solid var(--border, #d8dfd5);
+}
+
+.seo-faq summary {
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ink, #16211c);
+ padding-block: 10px;
+}
+
+.seo-faq details[open] summary {
+ margin-block-end: 4px;
+}
+
+.seo-faq details p {
+ margin: 0 0 12px;
+ color: var(--muted, #5c6b63);
+ font-size: 13px;
+ line-height: 1.7;
+}
+
+.seo-links {
+ margin-block-start: 20px;
+}
+
+.seo-links ul {
+ list-style: none;
+ margin: 0;
+ padding-inline-start: 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px 18px;
+}
+
+.seo-links a {
+ color: var(--link, #14725d);
+ text-underline-offset: 3px;
+}
+
+.seo-links a:focus-visible {
+ outline: 2px solid var(--focus, #1c6b4f);
+ outline-offset: 2px;
+ border-radius: 4px;
+}
diff --git a/app/components/views/deen-page.tsx b/app/components/views/deen-page.tsx
index 708d276..e7ce734 100644
--- a/app/components/views/deen-page.tsx
+++ b/app/components/views/deen-page.tsx
@@ -1,16 +1,18 @@
-// Deen page shell (STEP 10): Today/Library tabs over the two deen
-// views. Tab selection is in-memory only (never in the URL), so no
-// tracking state can leak into history, titles, or shared links.
+// Deen page shell (STEP 10): Today/Library/Review tabs over the three deen
+// views. Tab selection is in-memory only (never in the URL), so no tracking
+// state can leak into history, titles, or shared links. Review is the folded
+// /review (الحصاد) checklist, reachable now only through /deen.
"use client";
import { useState } from "react";
import { useT } from "../../lib/i18n";
import { DeenTodayView } from "./deen-today";
import { DeenLibraryView } from "./deen-library";
+import { ReviewTab } from "./review-tab";
export function DeenPage() {
const t = useT();
- const [tab, setTab] = useState<"today" | "library">("today");
+ const [tab, setTab] = useState<"today" | "library" | "review">("today");
return (
<>
@@ -32,8 +34,23 @@ export function DeenPage() {
>
{t("dn.library")}
+
- {tab === "today" ? : }
+ {tab === "today" ? (
+
+ ) : tab === "library" ? (
+
+ ) : (
+
+ )}
>
);
}
diff --git a/app/review/page.tsx b/app/components/views/review-tab.tsx
similarity index 92%
rename from app/review/page.tsx
rename to app/components/views/review-tab.tsx
index a837e73..f20b3fe 100644
--- a/app/review/page.tsx
+++ b/app/components/views/review-tab.tsx
@@ -1,14 +1,20 @@
-// Review page (/review): tri-state nightly checklist + score/mood/gratitude.
-// Submits write BOTH the per-day review record (enveloped) and the history
-// merge. Includes one honest night-context line for strong/low/return days.
-
+// Review tab (Deen journey, /deen): tri-state nightly checklist + score/mood/
+// gratitude. Submits write BOTH the per-day review record (enveloped) and the
+// history merge. Includes one honest night-context line for strong/low/return
+// days. Moved here from /review (now a permanent redirect to /deen).
"use client";
import { useEffect, useMemo, useState } from "react";
-import { dayId, hijriParts, loadFromStorage, QURAN_GOAL_PAGES, saveToStorage } from "../lib/wird";
-import { assessUser, selectGuidance } from "../lib/companion";
-import { useT } from "../lib/i18n";
-import { useWird } from "../components/wird-store";
+import {
+ dayId,
+ hijriParts,
+ loadFromStorage,
+ QURAN_GOAL_PAGES,
+ saveToStorage,
+} from "../../lib/wird";
+import { assessUser, selectGuidance } from "../../lib/companion";
+import { useT } from "../../lib/i18n";
+import { useWird } from "../wird-store";
type Status = "done" | "partial" | "missed";
type Mood = "good" | "ok" | "low" | null;
@@ -47,7 +53,7 @@ function loadReviews(): Record {
return loadFromStorage>("wird-reviews-v1", {});
}
-export default function ReviewPage() {
+export function ReviewTab() {
const t = useT();
const {
done,
@@ -118,8 +124,8 @@ export default function ReviewPage() {
const gradeTitle =
loadedScore >= 85 ? t("rv.g85") : loadedScore >= 60 ? t("rv.g60") : t("rv.gLow");
- // Night context (2.39): one honest line for the states that matter at
- // night — no card, no logging (Today owns the guidance log).
+ // Night context: one honest line for the states that matter at night — no
+ // card, no logging (Today owns the guidance log).
const nightLine = useMemo(() => {
try {
const todayId = dayId();
diff --git a/app/deen/page.tsx b/app/deen/page.tsx
index 51ccfcd..0cfa0b9 100644
--- a/app/deen/page.tsx
+++ b/app/deen/page.tsx
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// Deen route (/deen): the STEP 10 journey space — today dashboard plus
-// library/learning tabs (in-memory tab state, no URL params, static
-// generic title). Server component; views own all client state.
+// library/learning and nightly review tabs (in-memory tab state, no URL
+// params, static generic title). Server component; views own all client
+// state. Review was folded here from /review (permanent redirect).
import type { Metadata } from "next";
import { DeenPage } from "../components/views/deen-page";
@@ -9,11 +10,11 @@ export const metadata: Metadata = {
// Discreet by design: short generic title, one plain description of
// the public feature — never user data, never scores or ranks.
title: "دين",
- description: "رحلة الدين: مستويات تتبع ومهام وأعمال صالحة وتأمل خاص.",
+ description: "رحلة الدين: متابعة اليوم، أذكار وتعلم، ومراجعة ليلية خاصة.",
alternates: { canonical: "/deen" },
openGraph: {
title: "دين",
- description: "رحلة الدين: مستويات تتبع ومهام وأعمال صالحة وتأمل خاص.",
+ description: "رحلة الدين: متابعة اليوم، أذكار وتعلم، ومراجعة ليلية خاصة.",
url: "/deen",
images: [{ url: "/opengraph-image", width: 1200, height: 630, alt: "Wird — Daily Companion" }],
},
diff --git a/app/lib/__tests__/audit.test.ts b/app/lib/__tests__/audit.test.ts
index 128c7bf..4c81ab5 100644
--- a/app/lib/__tests__/audit.test.ts
+++ b/app/lib/__tests__/audit.test.ts
@@ -40,7 +40,6 @@ describe("route inventory (audit 4-5)", () => {
"/insights",
"/library",
"/recovery",
- "/review",
"/private-plans",
"/terms",
"/privacy",
@@ -66,7 +65,6 @@ describe("route inventory (audit 4-5)", () => {
for (const r of [
"/calendar",
"/insights",
- "/review",
"/deen",
"/library",
"/account",
@@ -80,7 +78,6 @@ describe("route inventory (audit 4-5)", () => {
"/",
"/calendar",
"/insights",
- "/review",
"/deen",
"/library",
"/account",
diff --git a/app/lib/__tests__/branch-guard.test.ts b/app/lib/__tests__/branch-guard.test.ts
new file mode 100644
index 0000000..3703936
--- /dev/null
+++ b/app/lib/__tests__/branch-guard.test.ts
@@ -0,0 +1,28 @@
+// Branch-security guard: the pre-push hook that blocks direct/forced
+// pushes to protected branches must exist and stay wired, and the
+// branch doc keeps the matching server-side settings. If this fails,
+// secured branches silently regressed.
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, it } from "vitest";
+
+const ROOT = join(__dirname, "..", "..", "..");
+const read = (p: string): string => readFileSync(join(ROOT, p), "utf8");
+
+describe("branch security", () => {
+ it("pre-push hook exists and blocks protected branches + force pushes", () => {
+ const hook = read(".husky/pre-push");
+ expect(hook, "blocks main").toContain("refs/heads/main");
+ expect(hook, "blocks develop").toContain("refs/heads/develop");
+ expect(hook, "blocks force push refs").toContain("+${local_ref#+}");
+ expect(hook, "escape hatch documented").toContain("ALLOW_PROTECTED_PUSH");
+ });
+ it("branching doc keeps the server-side settings and the check name", () => {
+ const doc = read("docs/BRANCHING.md");
+ expect(doc, "pr required").toMatch(/require a pull request/i);
+ expect(doc, "status check required").toMatch(/require status checks/i);
+ expect(doc, "ci check named").toContain("verify");
+ expect(doc, "no force pushes").toMatch(/do not allow force pushes/i);
+ expect(doc, "no deletions").toMatch(/do not allow deletions/i);
+ });
+});
diff --git a/app/lib/__tests__/seo.test.ts b/app/lib/__tests__/seo.test.ts
index c954403..94e53cd 100644
--- a/app/lib/__tests__/seo.test.ts
+++ b/app/lib/__tests__/seo.test.ts
@@ -40,16 +40,7 @@ describe("titles, descriptions, canonicals", () => {
expect(seen.has(t), `duplicate title "${t}" in ${rel} (first: ${seen.get(t)})`).toBe(false);
seen.set(t, rel);
}
- for (const r of [
- "calendar",
- "insights",
- "review",
- "library",
- "account",
- "terms",
- "privacy",
- "deen",
- ]) {
+ for (const r of ["calendar", "insights", "library", "account", "terms", "privacy", "deen"]) {
expect(
[...seen.values()].some((v) => v.startsWith(`${r}/`)),
`route ${r} titled`,
@@ -64,16 +55,7 @@ describe("titles, descriptions, canonicals", () => {
return read(`app/${r}/page.tsx`);
}
};
- for (const r of [
- "calendar",
- "insights",
- "review",
- "library",
- "account",
- "terms",
- "privacy",
- "deen",
- ]) {
+ for (const r of ["calendar", "insights", "library", "account", "terms", "privacy", "deen"]) {
expect(srcOf(r), `${r} description`).toMatch(/description:\s*"/);
}
// Recovery is fully discreet: no metadata export at all, so the
@@ -99,6 +81,53 @@ describe("titles, descriptions, canonicals", () => {
});
});
+describe("long-tail content, internal links, duplicate content", () => {
+ it("home carries a visible FAQ block with matching FAQPage JSON-LD", () => {
+ const src = read("app/page.tsx");
+ expect(src, "visible faq section").toContain('className="seo-faq"');
+ expect(src, "JsonLD FAQ type").toContain('"@type": "FAQPage"');
+ expect(src, "JsonLD answer shape").toContain("acceptedAnswer");
+ // Same strings render both the on-page list and the schema,
+ // so the two can never drift apart.
+ expect(
+ src.match(/\["q\d", "a\d"\]/g) ?? [],
+ "faq pairs in visible block + schema",
+ ).toHaveLength(10);
+ expect(src).toContain("seo.faq.${q}");
+ expect(src).toContain("seo.faq.${a}");
+ const strings = read("app/lib/strings.ts");
+ expect(strings.match(/seo\.faq\.q[1-5]/g) ?? [], "ar+en q keys").toHaveLength(10);
+ expect(strings.match(/seo\.faq\.a[1-5]/g) ?? [], "ar+en a keys").toHaveLength(10);
+ });
+ it("home links descriptively to every core route", () => {
+ const src = read("app/page.tsx");
+ for (const route of ["/library", "/deen", "/calendar", "/insights"]) {
+ expect(src, `internal link ${route}`).toContain(`Link href="${route}"`);
+ }
+ expect(src, "descriptive anchor text (nav.* labels)").toContain('t("nav.library")');
+ expect(src).not.toContain('href="/library">click here');
+ });
+ it("listed routes never share a description (no cannibalized queries)", () => {
+ const srcOf = (r: string): string => {
+ try {
+ return read(`app/${r}/layout.tsx`);
+ } catch {
+ return read(`app/${r}/page.tsx`);
+ }
+ };
+ const seen = new Map();
+ for (const r of ["calendar", "insights", "library", "account", "terms", "privacy", "deen"]) {
+ const d = srcOf(r).match(/description:\s*"([^"]+)"/);
+ if (!d) continue;
+ expect(
+ seen.has(d[1] ?? ""),
+ `duplicate description on ${r} (first: ${seen.get(d[1] ?? "")})`,
+ ).toBe(false);
+ seen.set(d[1] ?? "", r);
+ }
+ });
+});
+
describe("robots, sitemap, verification file", () => {
it("robots allows crawling and references the sitemap", () => {
const src = read("app/robots.ts");
diff --git a/app/lib/notify.ts b/app/lib/notify.ts
index ffe15d4..282f69c 100644
--- a/app/lib/notify.ts
+++ b/app/lib/notify.ts
@@ -41,7 +41,7 @@ export async function ensurePermission(): Promise {
}
}
-export function fireNotification(title: string, body: string, url = "/review"): void {
+export function fireNotification(title: string, body: string, url = "/deen"): void {
try {
if (!canNotify() || Notification.permission !== "granted") return;
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
diff --git a/app/lib/strings.ts b/app/lib/strings.ts
index 40b215d..9a6533f 100644
--- a/app/lib/strings.ts
+++ b/app/lib/strings.ts
@@ -8,7 +8,6 @@ const AR: Record = {
"nav.today": "اليوم",
"nav.calendar": "التقويم",
"nav.insights": "التقدّم",
- "nav.review": "الحصاد",
"nav.library": "الأذكار",
"nav.account": "حسابي",
"nav.skip": "تخطَّ إلى المحتوى",
@@ -160,6 +159,23 @@ const AR: Record = {
"night.journalPh": "اكتب لنفسك كلمة طيبة…",
"night.journalNote": "هذه المساحة لك وحدك، ولا تدخل في الإحصاءات.",
"footer.verse": "﴿ وَاذْكُر رَّبَّكَ كَثِيرًا وَسَبِّحْ بِالْعَشِيِّ وَالْإِبْكَارِ ﴾",
+ "seo.faq.title": "أسئلة شائعة عن وِرد",
+ "seo.faq.q1": "هل يحتاج تطبيق وِرد إلى اتصال بالإنترنت؟",
+ "seo.faq.a1":
+ "لا، يعمل وِرد دون إنترنت؛ الصلوات والأذكار والقرآن والبيانات كلها على جهازك. الإنترنت مطلوب فقط عند طلب تفسير أو حديث أو صوتية غير محمّلة مسبقًا.",
+ "seo.faq.q2": "أين تُخزَّن بياناتي، وهل تُرفع إلى خادم؟",
+ "seo.faq.a2":
+ "تُحفظ بياناتك على جهازك فقط (تخزين محلي)، ولا تُرسل إلى أي خادم. لا حسابات إلزامية، ولا أدوات تتبّع، ولا تحليلات خارجية.",
+ "seo.faq.q3": "هل أستطيع متابعة الصلوات والأذكار والقرآن في تطبيق واحد؟",
+ "seo.faq.a3":
+ "نعم، يجمع وِرد الصلوات والأذكار والقرآن والأهداف والمراجعة اليومية في شاشة اليوم، مع إحصاءات تفصّلها سعيك الصحيح دون كشفها لأحد.",
+ "seo.faq.q4": "هل وِرد عربي ويدعم اتجاه RTL؟",
+ "seo.faq.a4":
+ "نعم، وِرد عربي أولًا بواجهة كاملة الاتجاه من اليمين لليسار، مع إمكانية التبديل إلى الإنجليزية في أي وقت.",
+ "seo.faq.q5": "هل يعمل وِرد كتطبيق مستقل حتى دون إنترنت؟",
+ "seo.faq.a5":
+ "نعم، يمكنك تثبيته كتطبيق (PWA) من المتصفح؛ يعمل دون إنترنت ويحفظ بياناتك بين الجلسات دون حساب.",
+ "seo.links.title": "استكشف التطبيق أكثر",
"minplan.title": "خطة الحد الأدنى مفعّلة",
"minplan.sub": "الصلوات، ذكر قصير، آية واحدة، استغفار ١٠ مرات، والوتر. هذا يكفي لليوم.",
"minplan.cancel": "إلغاء",
@@ -1124,6 +1140,7 @@ const AR: Record = {
"dn.ach.disciplined": "المنضبط",
"dn.ach.disciplinedD": "حافظت على قاعدة شخصية 7 أيام.",
"dn.library": "المكتبة والتعلم",
+ "dn.review": "المراجعة",
"dn.mustahabb": "النمو بالمستحبات",
"dn.mustahabbSub":
"ممارسات مستحبة مجمعة للنمو. كل عنصر يوضح إن كان موثقا أو هدفا شخصيا — والأحكام تختلف بين المذاهب.",
@@ -1323,7 +1340,6 @@ const EN: Record = {
"nav.today": "Today",
"nav.calendar": "Calendar",
"nav.insights": "Progress",
- "nav.review": "Review",
"nav.library": "Adhkar",
"nav.account": "Account",
"nav.skip": "Skip to content",
@@ -1475,6 +1491,23 @@ const EN: Record = {
"night.journalPh": "Write yourself something kind…",
"night.journalNote": "Yours alone — excluded from stats.",
"footer.verse": "﴿ وَاذْكُر رَّبَّكَ كَثِيرًا وَسَبِّحْ بِالْعَشِيِّ وَالْإِبْكَارِ ﴾",
+ "seo.faq.title": "Frequently asked questions about Wird",
+ "seo.faq.q1": "Does Wird need an internet connection?",
+ "seo.faq.a1":
+ "No - Wird works offline; prayers, adhkar, Quran and your data all live on your device. Internet is only needed when you request a tafsir, hadith, or audio not bundled in advance.",
+ "seo.faq.q2": "Where is my data stored, and is it uploaded to a server?",
+ "seo.faq.a2":
+ "Your data is stored on your device only (local storage) and is never sent to a server. No mandatory accounts, no tracking, no third-party analytics.",
+ "seo.faq.q3": "Can I track prayers, adhkar and Quran in one app?",
+ "seo.faq.a3":
+ "Yes - Wird brings prayers, adhkar, Quran, goals and the daily review into one Today screen, with statistics that stay private to you.",
+ "seo.faq.q4": "Is Wird in Arabic and does it support RTL?",
+ "seo.faq.a4":
+ "Yes - Wird is Arabic-first with a full right-to-left interface, and you can switch to English at any time.",
+ "seo.faq.q5": "Does Wird work as a standalone app even offline?",
+ "seo.faq.a5":
+ "Yes - you can install it as a PWA from the browser; it works offline and keeps your data between sessions without an account.",
+ "seo.links.title": "Explore the app further",
"minplan.title": "Minimum plan active",
"minplan.sub": "Prayers, short dhikr, one verse, 10 istighfar, and Witr. Enough for today.",
"minplan.cancel": "Cancel",
@@ -2462,6 +2495,7 @@ const EN: Record = {
"dn.ach.disciplined": "Disciplined",
"dn.ach.disciplinedD": "Kept a personal discipline rule for 7 days.",
"dn.library": "Library and learning",
+ "dn.review": "Review",
"dn.mustahabb": "Mustahabb growth",
"dn.mustahabbSub":
"Recommended practices grouped for growth. Each item shows whether it is sourced or a personal goal - rulings vary by school.",
diff --git a/app/lib/wird.ts b/app/lib/wird.ts
index 316e755..218b57c 100644
--- a/app/lib/wird.ts
+++ b/app/lib/wird.ts
@@ -213,7 +213,6 @@ export const NAV_ITEMS: [string, string, string][] = [
["today", "⌂", "اليوم"],
["calendar", "▦", "التقويم"],
["insights", "↗", "التقدّم"],
- ["review", "☾", "الحصاد"],
["deen", "✦", "الدين"],
["library", "◈", "الأذكار"],
["account", "◌", "حسابي"],
@@ -223,7 +222,6 @@ export const NAV_HREFS: Record = {
today: "/",
calendar: "/calendar",
insights: "/insights",
- review: "/review",
deen: "/deen",
library: "/library",
account: "/account",
diff --git a/app/page.tsx b/app/page.tsx
index 30286da..9e4c9b3 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -12,6 +12,7 @@ import { fastLabel, PRAYER_ID } from "./lib/daymode";
import { EditModal } from "./components/edit-modal";
import { NowView } from "./components/views/now";
import { PrayerArc } from "./components/prayer-arc";
+import Link from "next/link";
// Inline vault unlock (locked reflections): password + button only, generic
// failure message. Session-scoped — reload locks again by design.
@@ -429,7 +430,7 @@ export default function TodayPage() {
const handleGuideAction = (kind: string) => {
try {
if (kind === "rescue") scrollToId("rescue-plan");
- else if (kind === "review") router.push("/review");
+ else if (kind === "review") router.push("/deen");
else if (kind === "core") {
setMinimumPlan(true);
scrollToId("rescue-plan");
@@ -1794,6 +1795,65 @@ export default function TodayPage() {
onDelete={deleteEditing}
/>
)}
+
+ {t("seo.faq.title")}
+ {(
+ [
+ ["q1", "a1"],
+ ["q2", "a2"],
+ ["q3", "a3"],
+ ["q4", "a4"],
+ ["q5", "a5"],
+ ] as Array<[string, string]>
+ ).map(([q, a]) => (
+
+ {t(`seo.faq.${q}`)}
+ {t(`seo.faq.${a}`)}
+
+ ))}
+
+