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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ 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 —
Expand Down
16 changes: 10 additions & 6 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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,
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions MASTER_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 يومًا) + سلّم تنبيهات محلية + مواقيت يدوية بعدّادات حية + وضع المسجد.
Expand Down
27 changes: 22 additions & 5 deletions app/components/views/deen-page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className="deen-tabs" role="tablist" aria-label={t("dn.title")}>
Expand All @@ -32,8 +34,23 @@ export function DeenPage() {
>
{t("dn.library")}
</button>
<button
type="button"
role="tab"
aria-selected={tab === "review"}
onClick={() => setTab("review")}
className={tab === "review" ? "selected" : ""}
>
{t("dn.review")}
</button>
</div>
{tab === "today" ? <DeenTodayView /> : <DeenLibraryView />}
{tab === "today" ? (
<DeenTodayView />
) : tab === "library" ? (
<DeenLibraryView />
) : (
<ReviewTab />
)}
</>
);
}
28 changes: 17 additions & 11 deletions app/review/page.tsx → app/components/views/review-tab.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -47,7 +53,7 @@ function loadReviews(): Record<string, SavedReview> {
return loadFromStorage<Record<string, SavedReview>>("wird-reviews-v1", {});
}

export default function ReviewPage() {
export function ReviewTab() {
const t = useT();
const {
done,
Expand Down Expand Up @@ -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();
Expand Down
9 changes: 5 additions & 4 deletions app/deen/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
// 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";

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" }],
},
Expand Down
3 changes: 0 additions & 3 deletions app/lib/__tests__/audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ describe("route inventory (audit 4-5)", () => {
"/insights",
"/library",
"/recovery",
"/review",
"/private-plans",
"/terms",
"/privacy",
Expand All @@ -66,7 +65,6 @@ describe("route inventory (audit 4-5)", () => {
for (const r of [
"/calendar",
"/insights",
"/review",
"/deen",
"/library",
"/account",
Expand All @@ -80,7 +78,6 @@ describe("route inventory (audit 4-5)", () => {
"/",
"/calendar",
"/insights",
"/review",
"/deen",
"/library",
"/account",
Expand Down
35 changes: 4 additions & 31 deletions app/lib/__tests__/seo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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
Expand Down Expand Up @@ -119,7 +101,7 @@ describe("long-tail content, internal links, duplicate content", () => {
});
it("home links descriptively to every core route", () => {
const src = read("app/page.tsx");
for (const route of ["/library", "/deen", "/review", "/insights"]) {
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")');
Expand All @@ -134,16 +116,7 @@ describe("long-tail content, internal links, duplicate content", () => {
}
};
const seen = new Map<string, string>();
for (const r of [
"calendar",
"insights",
"review",
"library",
"account",
"terms",
"privacy",
"deen",
]) {
for (const r of ["calendar", "insights", "library", "account", "terms", "privacy", "deen"]) {
const d = srcOf(r).match(/description:\s*"([^"]+)"/);
if (!d) continue;
expect(
Expand Down
2 changes: 1 addition & 1 deletion app/lib/notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function ensurePermission(): Promise<boolean> {
}
}

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) {
Expand Down
4 changes: 2 additions & 2 deletions app/lib/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const AR: Record<string, string> = {
"nav.today": "اليوم",
"nav.calendar": "التقويم",
"nav.insights": "التقدّم",
"nav.review": "الحصاد",
"nav.library": "الأذكار",
"nav.account": "حسابي",
"nav.skip": "تخطَّ إلى المحتوى",
Expand Down Expand Up @@ -1141,6 +1140,7 @@ const AR: Record<string, string> = {
"dn.ach.disciplined": "المنضبط",
"dn.ach.disciplinedD": "حافظت على قاعدة شخصية 7 أيام.",
"dn.library": "المكتبة والتعلم",
"dn.review": "المراجعة",
"dn.mustahabb": "النمو بالمستحبات",
"dn.mustahabbSub":
"ممارسات مستحبة مجمعة للنمو. كل عنصر يوضح إن كان موثقا أو هدفا شخصيا — والأحكام تختلف بين المذاهب.",
Expand Down Expand Up @@ -1340,7 +1340,6 @@ const EN: Record<string, string> = {
"nav.today": "Today",
"nav.calendar": "Calendar",
"nav.insights": "Progress",
"nav.review": "Review",
"nav.library": "Adhkar",
"nav.account": "Account",
"nav.skip": "Skip to content",
Expand Down Expand Up @@ -2496,6 +2495,7 @@ const EN: Record<string, string> = {
"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.",
Expand Down
2 changes: 0 additions & 2 deletions app/lib/wird.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ export const NAV_ITEMS: [string, string, string][] = [
["today", "⌂", "اليوم"],
["calendar", "▦", "التقويم"],
["insights", "↗", "التقدّم"],
["review", "☾", "الحصاد"],
["deen", "✦", "الدين"],
["library", "◈", "الأذكار"],
["account", "◌", "حسابي"],
Expand All @@ -223,7 +222,6 @@ export const NAV_HREFS: Record<string, string> = {
today: "/",
calendar: "/calendar",
insights: "/insights",
review: "/review",
deen: "/deen",
library: "/library",
account: "/account",
Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,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");
Expand Down Expand Up @@ -1821,7 +1821,7 @@ export default function TodayPage() {
<Link href="/deen">{t("nav.deen")}</Link>
</li>
<li>
<Link href="/review">{t("nav.review")}</Link>
<Link href="/calendar">{t("nav.calendar")}</Link>
</li>
<li>
<Link href="/insights">{t("nav.insights")}</Link>
Expand Down
20 changes: 0 additions & 20 deletions app/review/layout.tsx

This file was deleted.

1 change: 0 additions & 1 deletion app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const routes = [
"",
"/calendar",
"/insights",
"/review",
"/deen",
"/library",
"/account",
Expand Down
Loading
Loading