Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0714e50
First commit for the quran syntax change.
smshahidraza May 11, 2026
68e00bd
First commit for the quran syntax change using MCP.
smshahidraza May 12, 2026
ea620b4
Removed OpenAI call and Sarf-verb chart
smshahidraza May 12, 2026
0ce3897
Replaced icon with search icon.
smshahidraza May 13, 2026
5bd74ff
Added markdown file about the design and list of file added/updated.
smshahidraza May 14, 2026
6cae285
Removed changes needed for local setup and updated doc.
smshahidraza May 16, 2026
d6e35b6
ci: pin Node.js to 18 via .nvmrc for Netlify builds
smshahidraza May 18, 2026
e97acb8
Added redirects for netlify
smshahidraza May 19, 2026
912bebd
added netlify.toml to resolve 404 error in netlify
smshahidraza May 19, 2026
c0d838f
added netlify.toml and redirect at root level
smshahidraza May 19, 2026
35dd1a8
Updated netlify based on the AI suggestion
smshahidraza May 19, 2026
217e7e3
Updated files for fixing yarn build issue
smshahidraza May 19, 2026
b6f33b4
Updated netlify to fix folder issue
smshahidraza May 19, 2026
662fa43
Added environment variable in netlify
smshahidraza May 19, 2026
bb12c32
Added environment variable in netlify
smshahidraza May 19, 2026
854f743
Added environment variable in netlify
smshahidraza May 19, 2026
6088346
Added environment variable in netlify
smshahidraza May 19, 2026
5b02bb0
Added environment variable in netlify
smshahidraza May 19, 2026
67f45a8
Updated the nodejs version for vercel
smshahidraza May 19, 2026
860a905
Added vercel.json for vercel
smshahidraza May 19, 2026
fc2feb5
Updated the 404 logic for vercel
smshahidraza May 19, 2026
f1c362a
Fixed build issue for varcel
smshahidraza May 19, 2026
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
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ NEXT_PUBLIC_QURAN_REFLECT_URL=https://quranreflect.com

NEXT_PUBLIC_SSO_ENABLED=false

NEXT_PUBLIC_EMBED_URL=https://quran.com/embed/v1 # Embed Ayah
NEXT_PUBLIC_EMBED_URL=https://quran.com/embed/v1 # Embed Ayah

# Optional: Syntax tab β€” morphology + paradigm + charts via Quran MCP (https://mcp.quran.ai/)
# QURAN_SYNTAX_MCP_URL=https://mcp.quran.ai/
#
# Set to true to skip MCP β€” uses pasted JSON from src/services/syntaxAnalysis.mock.ts
# NEXT_PUBLIC_SYNTAX_ANALYSIS_MOCK=true
198 changes: 198 additions & 0 deletions docs/syntax-study-mode-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Syntax analysis & Study Mode β€” design notes

This document describes the feature work on the **current branch** compared to **`origin/production`**: a new **Syntax** experience in Quran Reader **Study Mode**, backed by a **Next.js API route** that calls **Quran MCP** for morphology (and optional paradigm-derived charts).

**Baseline:** `git diff origin/production...HEAD`
**External reference:** [Quran MCP documentation](https://mcp.quran.ai/documentation) (Streamable HTTP, `fetch_grounding_rules`, `fetch_word_morphology`, `fetch_word_paradigm`).

---

## 1. Summary

| Area | Change |
|------|--------|
| **Study Mode** | New bottom tab **`syntax`** with search-style icon; opens grammatical analysis for the **selected word** in the verse. |
| **UI** | New **Syntax** view: layout/skeleton, morphology text, optional **sarf / verb / ism** charts (types in `types/SyntaxAnalysis.ts`). |
| **API** | `POST /api/syntax/analyze` β€” validates input, calls server-side Quran MCP client, returns `SyntaxAnalysisResult`. |
| **Data** | Morphology from MCP mapped to `rootLetter`, `pattern`, `wordBreakDown`; paradigm stems optionally merged into charts via `syntaxChartsFromMcp` + `syntaxAnalysisCharts`. |
| **Mock** | `NEXT_PUBLIC_SYNTAX_ANALYSIS_MOCK=true` skips MCP and returns `syntaxAnalysis.mock.ts`. |
| **Infra / other** | `@modelcontextprotocol/sdk` dependency; TS path alias for MCP ESM; Verse font tweak in tafsir/translation mode. `middleware.ts` and `url.ts` match **`origin/production`** (no Syntax-specific edits). |

---

## 2. User-facing flow (Study Mode)

1. User opens **Study Mode** on a verse (existing flow).
2. User selects a **word** in the verse (word tap / selection used elsewhere for tafsir, etc.).
3. User taps the **Syntax** tab (icon: `public/icons/search.svg`, same `color` pattern as Tafsir’s `BookIcon`).
4. **`StudyModeSyntaxTab`** loads **`SyntaxBody`** (dynamic import + skeleton).
5. **`SyntaxBody`** uses **SWR** with a key derived from `selectedWord` location, Uthmani text, and `verseKey`.
6. **`fetchSyntaxAnalysis`** posts to **`/api/syntax/analyze`** unless mock mode is on.
7. Response drives **morphology** copy and **`SyntaxAnalysisCharts`** when `verb*`, `sarfChart`, or `ismChart` are present and pass normalization.

**Key files:** `StudyModeBottomActions` (`StudyModeTabId.SYNTAX`), `StudyModeBodyTabs.tsx`, `StudyModeBody.tsx`, `StudyModeModal/index.tsx`, `tabs/StudyModeSyntaxTab.tsx`, `SyntaxView/*`.

---

## 3. API: `POST /api/syntax/analyze`

| Item | Detail |
|------|--------|
| **Path** | `/api/syntax/analyze` |
| **File** | `src/pages/api/syntax/analyze.ts` |
| **Method** | `POST` only (`405` otherwise). |
| **Body (JSON)** | `{ "textUthmani": string, "verseKey"?: string }` |
| **Validation** | `textUthmani` required, non-empty after trim; max length **200** characters. |
| **Success** | `200` + `SyntaxAnalysisResult` |
| **Errors** | `400` (bad input), `502` + `{ error: string }` (MCP failure / thrown error). |

**`verseKey` format:** When present, should match `surah:ayah` (e.g. `2:255`) so morphology and paradigm calls can use **ayah-scoped** MCP arguments (see `isValidAyahKey` in morphology helper).

**Client:** `src/services/syntaxAnalysisService.ts` β€” `fetch('/api/syntax/analyze', { method: 'POST', ... })`, throws `Error` with server message when `!res.ok` or body contains `error`.

---

## 4. Quran MCP server flow

All MCP calls run **on the server** inside `fetchSyntaxAnalysisViaQuranMcp` (`src/lib/syntaxAnalysisQuranMcp.ts`) so the browser never holds MCP transport credentials beyond same-origin API.

### 4.1 Connection

- **Transport:** `@modelcontextprotocol/sdk` `StreamableHTTPClientTransport`.
- **URL:** `process.env.QURAN_SYNTAX_MCP_URL` or default `https://mcp.quran.ai/` (trailing slash normalized).
- **Client name:** `quran.com-frontend` (version `1.0.0`).

### 4.2 Tool sequence (`runMcpSyntaxStudyOnClient` in `syntaxAnalysisQuranMcpMorphology.ts`)

1. **`fetch_grounding_rules`** β€” session grounding per Quran MCP docs.
2. **`fetch_word_morphology`**
- If `verseKey` is valid `surah:ayah`: arguments `ayah_key`, `word_text`.
- Else: argument `word` = Uthmani text.
3. **Parse** structured payload β†’ `words[]`; **pick** row matching `textUthmani` (`pickMorphologyWord`).
4. **`fetch_word_paradigm`** (best-effort; may return `null` on error)
- If valid ayah key: `ayah_key` + `word_text`.
- Else: `lemma` from picked word when available.
5. **Return bundle:** `base` (`SyntaxAnalysisResult` from morphology), `pickedWord`, `morphologyResponse`, `paradigm`.

### 4.3 Mapping morphology β†’ `SyntaxAnalysisResult`

- **`morphologyWordToSyntaxResult`:** builds `rootLetter`, `pattern` (English line from `description` / `grammatical_features`, Arabic-ish `patternType` from POS/aspect/case), `wordBreakDown` from `morpheme_segments` or whole word + translation.

### 4.4 Optional charts

- **`syntaxChartsFromMcp.ts`** β€” builds partial chart objects from **paradigm** stems (`perfect` / `imperfect` / `imperative`) and picked word metadata where applicable.
- **`syntaxAnalysisCharts.ts`** β€” `applyOptionalChartsToResult`, `normalizeSarfChart`, `normalizeVerbChart`, `normalizeIsmChart`, and verb present/past merge rules aligned with the UI tables.

`fetchSyntaxAnalysisViaQuranMcp` merges: `applyOptionalChartsToResult(bundle.base, buildOptionalChartsFromMcp(bundle.pickedWord, bundle.paradigm))`.

---

## 5. Types & UI charts

- **`types/SyntaxAnalysis.ts`** β€” `SyntaxAnalysisResult` and optional `sarfChart`, `verbPresentTenseChart`, `verbPastTenseChart`, `verbChart`, `ismChart`.
- **`SyntaxChartTables.tsx`** β€” renders grids when props are defined.
- **`useSyntaxChartArabicTypography.ts`** β€” shared Arabic typography for Syntax view.

---

## 6. Configuration & mock

| Variable | Role |
|----------|------|
| `QURAN_SYNTAX_MCP_URL` | Optional override for MCP Streamable HTTP base URL. |
| `NEXT_PUBLIC_SYNTAX_ANALYSIS_MOCK` | When `true`, client returns `SYNTAX_ANALYSIS_MOCK_RESPONSE` (no `/api/syntax/analyze` call). |

Documented in `.env.example` (syntax / MCP section).

---

## 7. Middleware & URL utilities (current behavior, same as production)

These files are **not** part of the Syntax feature contract. They are documented here because an earlier branch revision briefly changed them; the **current** sources match **`origin/production`**.

### 7.1 `src/middleware.ts`

| Behavior | Detail |
|----------|--------|
| **`_next/data` requests** | If `req.url` includes `_next/data`, respond with **`404`** and an empty body. Intended to force a **full page reload** after a new deployment instead of serving stale client-side navigation payloads. Applies in **all** environments (including local `yarn dev`), not gated on `NODE_ENV`. |
| **Ramadan routes** | Paths containing `/ramadan2026` or `/ramadanchallenge` (case-insensitive) redirect to the **lowercase** pathname when the URL is not already lowercase. |
| **Everything else** | `NextResponse.next()`. |

**Implication for local dev:** Client transitions that rely on `_next/data` JSON may get `404` from middleware; a hard refresh or full navigation is expected after deploys. This is unrelated to `/api/syntax/analyze`.

### 7.2 `src/utils/url.ts`

`getProxiedServiceUrl(service, path)` builds backend URLs for Quran Foundation services. There is **no** special branch for `QuranFoundationService.CONTENT` that bypasses the app proxy.

| Condition | Base URL |
|-----------|----------|
| **Static build** (`isStaticBuild`) | `${API_GATEWAY_URL}/${service}${path}` |
| **Otherwise** | `${getBasePath()}/api/proxy/${service}${path}` where `getBasePath()` is `http://` or `https://` + `NEXT_PUBLIC_VERCEL_URL` depending on `NEXT_PUBLIC_VERCEL_ENV === 'development'`. |

All services in `QuranFoundationService` (`search`, `auth`, `content`, `quran-reflect`) use the same proxy pattern. Syntax analysis does **not** call this helper; it uses **`POST /api/syntax/analyze`** β†’ Quran MCP on the server.

### 7.3 Other branch diffs (Syntax-related infra)

- **`tsconfig.json`** β€” path alias `@modelcontextprotocol/sdk/*` β†’ ESM dist (bundler resolution).
- **`package.json` / `yarn.lock`** β€” adds `@modelcontextprotocol/sdk`.
- **`VerseText.module.scss`** β€” mobile `tafsirOrTranslationMode` font scale factor adjusted (`1.2` β†’ `0.75` of `--font-size`).

---

## 8. File change list (`origin/production...HEAD`)

| Status | Path | Short description |
|--------|------|---------------------|
| M | `.env.example` | Document Quran MCP URL and syntax mock flag. |
| M | `package.json` | Add `@modelcontextprotocol/sdk`. |
| M | `yarn.lock` | Lockfile for new dependency. |
| M | `tsconfig.json` | MCP SDK path alias. |
| M | `src/components/Verse/VerseText.module.scss` | Tafsir/translation mode font sizing tweak. |
| M | `src/components/QuranReader/ReadingView/StudyModeModal/StudyModeBody.tsx` | Wire Syntax tab panel / props (e.g. `selectedWord`). |
| M | `src/components/QuranReader/ReadingView/StudyModeModal/StudyModeBodyTabs.tsx` | Register `StudyModeSyntaxTab`, tab config, **Search** icon for Syntax. |
| M | `src/components/QuranReader/ReadingView/StudyModeModal/StudyModeBottomActions/index.tsx` | Add `StudyModeTabId.SYNTAX`. |
| M | `src/components/QuranReader/ReadingView/StudyModeModal/index.tsx` | Study Mode state / layout for Syntax tab. |
| A | `.../tabs/StudyModeSyntaxTab.tsx` | Lazy tab shell: scroll container + dynamic `SyntaxBody`. |
| A | `src/components/QuranReader/SyntaxView/SyntaxBody.tsx` | SWR + morphology UI + charts. |
| A | `src/components/QuranReader/SyntaxView/SyntaxChartTables.tsx` | Sarf / verb / ism tables. |
| A | `src/components/QuranReader/SyntaxView/SyntaxSkeleton.tsx` | Loading UI for dynamic import. |
| A | `src/components/QuranReader/SyntaxView/SyntaxSkeleton.module.scss` | Skeleton styles. |
| A | `src/components/QuranReader/SyntaxView/SyntaxTabLayout.tsx` | Shared tab layout / scroll hook export. |
| A | `src/components/QuranReader/SyntaxView/SyntaxTabLayout.module.scss` | Layout styles. |
| A | `src/components/QuranReader/SyntaxView/SyntaxView.module.scss` | Syntax body styles. |
| A | `src/components/QuranReader/SyntaxView/useSyntaxChartArabicTypography.ts` | Arabic font helpers for charts/body. |
| A | `src/lib/syntaxAnalysisCharts.ts` | Normalize + merge optional charts onto base result. |
| A | `src/lib/syntaxAnalysisQuranMcp.ts` | MCP client connect + fetch bundle + merge charts. |
| A | `src/lib/syntaxAnalysisQuranMcpMorphology.ts` | MCP tool calls, pick word, map to `SyntaxAnalysisResult`. |
| A | `src/lib/syntaxChartsFromMcp.ts` | Build chart-shaped JSON from paradigm + picked word. |
| A | `src/pages/api/syntax/analyze.ts` | POST API handler calling `fetchSyntaxAnalysisViaQuranMcp`. |
| A | `src/services/syntaxAnalysis.mock.ts` | Full mock `SyntaxAnalysisResult` for local UI. |
| A | `src/services/syntaxAnalysisService.ts` | Client fetch + mock gate + `getWordTextUthmaniForSyntax`. |
| A | `types/SyntaxAnalysis.ts` | Shared TS types for API + UI. |

---

## 9. Diagram (high level)

```mermaid
sequenceDiagram
participant U as Browser
participant SB as SyntaxBody
participant API as POST /api/syntax/analyze
participant MCP as Quran MCP (Streamable HTTP)

U->>SB: Select word, open Syntax tab
SB->>API: JSON textUthmani, verseKey
API->>MCP: connect + grounding + morphology + paradigm
MCP-->>API: words + paradigm payload
API-->>SB: SyntaxAnalysisResult (+ optional charts)
SB-->>U: Render morphology + charts
```

---

## 10. Maintenance notes

- Regenerate this file list anytime with:
`git fetch origin production && git diff --name-status origin/production...HEAD`
- If MCP tools or response shapes change upstream, update **`syntaxAnalysisQuranMcpMorphology.ts`** and **`syntaxChartsFromMcp.ts`** together so the API contract in **`types/SyntaxAnalysis.ts`** stays satisfied.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@milkdown/react": "^7.5.0",
"@milkdown/transformer": "^7.5.0",
"@milkdown/utils": "^7.5.0",
"@modelcontextprotocol/sdk": "^1.12.0",
"@next/bundle-analyzer": "^14.2.7",
"@novu/headless": "0.24.0",
"@radix-ui/react-checkbox": "^1.1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ const StudyModeBody: React.FC<StudyModeBodyProps> = ({
<TabComponent
chapterId={selectedChapterId}
verseNumber={selectedVerseNumber}
selectedWord={selectedWord}
switchTab={onTabChange}
questionId={questionId}
questionsInitialData={questionsInitialData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import LightbulbOnIcon from '@/icons/lightbulb-on.svg';
import LightbulbIcon from '@/icons/lightbulb.svg';
import QiraatIcon from '@/icons/qiraat-icon.svg';
import RelatedVerseIcon from '@/icons/related-verses.svg';
import SearchIcon from '@/icons/search.svg';
import { AyahHadithsResponse } from '@/types/Hadith';
import AyahQuestionsResponse from '@/types/QuestionsAndAnswers/AyahQuestionsResponse';
import Word from '@/types/Word';
import QuestionType from '@/types/QuestionsAndAnswers/QuestionType';
import { toLocalizedNumber } from '@/utils/locale';

Expand All @@ -36,6 +38,10 @@ export const StudyModeTafsirTab = dynamic(() => import('./tabs/StudyModeTafsirTa
loading: Loading,
});

export const StudyModeSyntaxTab = dynamic(() => import('./tabs/StudyModeSyntaxTab'), {
loading: Loading,
});

export const StudyModeReflectionsTab = dynamic(() => import('./tabs/StudyModeReflectionsTab'), {
loading: Loading,
});
Expand Down Expand Up @@ -68,6 +74,7 @@ export const StudyModeRelatedVersesTab = dynamic(
interface TabProps {
chapterId: string;
verseNumber: string;
selectedWord?: Word;
switchTab?: (tabId: StudyModeTabId | null) => void;
questionId?: string;
questionsInitialData?: AyahQuestionsResponse;
Expand All @@ -79,6 +86,7 @@ interface TabProps {

export const TAB_COMPONENTS: Partial<Record<StudyModeTabId, React.ComponentType<TabProps>>> = {
[StudyModeTabId.TAFSIR]: StudyModeTafsirTab,
[StudyModeTabId.SYNTAX]: StudyModeSyntaxTab,
[StudyModeTabId.LAYERS]: StudyModeLayersTab,
[StudyModeTabId.REFLECTIONS]: StudyModeReflectionsTab,
[StudyModeTabId.LESSONS]: StudyModeLessonsTab,
Expand Down Expand Up @@ -156,6 +164,13 @@ export const useStudyModeTabs = ({
onClick: () => handleTabClick(StudyModeTabId.TAFSIR),
condition: true,
},
{
id: StudyModeTabId.SYNTAX,
label: 'Syntax',
icon: <SearchIcon color="var(--color-blue-buttons-and-icons)" />,
onClick: () => handleTabClick(StudyModeTabId.SYNTAX),
condition: true,
},
{
id: StudyModeTabId.LAYERS,
label: t('quran-reader:layers.title'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AudioPlayerMachineContext } from 'src/xstate/AudioPlayerMachineContext'

export enum StudyModeTabId {
TAFSIR = 'tafsir',
SYNTAX = 'syntax',
LAYERS = 'layers',
LESSONS = 'lessons',
REFLECTIONS = 'reflections',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ const StudyModeModal: React.FC<Props> = ({
activeContentTab &&
[
StudyModeTabId.TAFSIR,
StudyModeTabId.SYNTAX,
StudyModeTabId.LAYERS,
StudyModeTabId.REFLECTIONS,
StudyModeTabId.LESSONS,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import dynamic from 'next/dynamic';

import SyntaxSkeleton from '@/components/QuranReader/SyntaxView/SyntaxSkeleton';
import { useSyntaxTabScroll, syntaxTabStyles as styles } from '@/components/QuranReader/SyntaxView/SyntaxTabLayout';
import Word from '@/types/Word';

const SyntaxBody = dynamic(() => import('@/components/QuranReader/SyntaxView/SyntaxBody'), {
loading: SyntaxSkeleton,
});

interface StudyModeSyntaxTabProps {
chapterId: string;
verseNumber: string;
selectedWord?: Word;
}

const StudyModeSyntaxTab: React.FC<StudyModeSyntaxTabProps> = ({
chapterId,
verseNumber,
selectedWord,
}) => {
const { containerRef, scrollToTop } = useSyntaxTabScroll();

return (
<div ref={containerRef} className={styles.container}>
<SyntaxBody
chapterId={chapterId}
verseNumber={verseNumber}
selectedWord={selectedWord}
scrollToTop={scrollToTop}
/>
</div>
);
};

export default StudyModeSyntaxTab;
Loading