Skip to content
Open
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
9 changes: 9 additions & 0 deletions .gitflow
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main]
branches: [main, develop]
pull_request:

jobs:
Expand Down
20 changes: 20 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -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
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
26 changes: 24 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
61 changes: 61 additions & 0 deletions app/additions.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
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
28 changes: 28 additions & 0 deletions app/lib/__tests__/branch-guard.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading
Loading