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
60 changes: 0 additions & 60 deletions .github/workflows/bootstrap-source.yml

This file was deleted.

35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

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

permissions:
contents: read

concurrency:
group: math-cs-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
name: Verify application
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --include=optional

- name: Run quality gate
run: npm run verify
58 changes: 58 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Publish GitHub Pages site

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: write

concurrency:
group: math-cs-pages-source
cancel-in-progress: true

jobs:
publish:
name: Verify and publish static site
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --include=optional

- name: Verify application
run: npm run verify

- name: Build GitHub Pages site
run: npm run build:pages

- name: Upload generated site artifact
uses: actions/upload-artifact@v4
with:
name: math-cs-pages-site
path: site
if-no-files-found: error

- name: Commit generated site to main
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A site
if git diff --cached --quiet; then
echo "Generated Math-CS site is already current."
else
git commit -m "chore: publish Math-CS Pages site [skip ci]"
git push origin HEAD:main
fi
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Server build artifacts
server/node_modules
server/dist
server/.env
1 change: 0 additions & 1 deletion .migration-seed

This file was deleted.

File renamed without changes.
1 change: 1 addition & 0 deletions .pages-release
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generated GitHub Pages output for Inmerson/Math-CS from main.
1 change: 0 additions & 1 deletion .worktree-note

This file was deleted.

4 changes: 4 additions & 0 deletions App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';
import App from './App';
describe('Math-CS app shell', () => { beforeEach(() => localStorage.clear()); it('provides an accessible notebook shell', () => { render(<App />); expect(screen.getAllByRole('main')).toHaveLength(1); expect(screen.getAllByRole('heading', { level: 1 })).toHaveLength(1); expect(screen.getByLabelText('Primary navigation')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Open Math I' })).toBeVisible(); expect(document.querySelector('[aria-hidden="true"]')).not.toBeNull(); }); });
65 changes: 65 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useState } from 'react';
import { Menu } from 'lucide-react';
import { AnimatedBackground } from './components/AnimatedBackground';
import { Sidebar } from './components/Sidebar';
import { BottomNavigation } from './components/layout/BottomNavigation';
import { DashboardView } from './views/DashboardView';
import { CourseHubView } from './views/CourseHubView';
import { LessonWorkspaceView } from './views/LessonWorkspaceView';
import { MathLabView } from './views/MathLabView';
import { PracticeView } from './views/PracticeView';
import { ExamsView } from './views/ExamsView';
import { ProgressView } from './views/ProgressView';
import { FormulaWorkspaceView } from './views/FormulaWorkspaceView';
import { AIChatView } from './views/AIChatView';
import { QuizSubmission } from './components/quiz/QuizRunner';
import { AppDestination } from './types';
import { getCourse, getTopic } from './data/courseCatalog';
import { createDefaultMathCsState, loadMathCsState, MathCsState, saveMathCsState } from './utils/mathCsStorage';
import { markTopicComplete, recordQuizResult, removeFormula, saveFormula, saveFormulaNote } from './utils/progress';

const isValidDestination = (destination: AppDestination): boolean => {
if (destination.section === 'course') return Boolean(destination.courseId && getCourse(destination.courseId));
if (destination.section === 'lesson') return Boolean(destination.courseId && destination.topicId && getTopic(destination.courseId, destination.topicId));
return true;
};


const App: React.FC = () => {
const initial = typeof window === 'undefined' ? createDefaultMathCsState() : loadMathCsState();
const [state, setState] = useState<MathCsState>(initial);
const stored = initial.lastDestination as AppDestination | null;
const [destination, setDestination] = useState<AppDestination>(stored && isValidDestination(stored) ? stored : { section: 'dashboard' });
const [menuOpen, setMenuOpen] = useState(false);

const updateState = (next: MathCsState) => { setState(next); saveMathCsState(next); };
const navigate = (next: AppDestination) => {
const safe = isValidDestination(next) ? next : { section: 'dashboard' } as AppDestination;
setDestination(safe);
updateState({ ...state, lastDestination: safe });
setMenuOpen(false);
};

const handleQuiz = (result: QuizSubmission) => {
const withResult = recordQuizResult(state, result.courseId, result.topicId, result.score, result.total);
updateState(result.percentage >= 70 ? markTopicComplete(withResult, result.topicId) : withResult);
};

const renderContent = () => {
if (!isValidDestination(destination)) return <div className="mx-auto max-w-xl text-center"><h1 className="text-3xl font-bold text-white">Learning path not found</h1><p className="mt-3 text-slate-400">Choose a safe destination to continue.</p><div className="mt-5 flex justify-center gap-3"><button className="focus-ring rounded-lg bg-cyan-300 px-4 py-2 text-slate-950" onClick={() => navigate({ section: 'dashboard' })}>Dashboard</button><button className="focus-ring rounded-lg border border-white/10 px-4 py-2" onClick={() => navigate({ section: 'course', courseId: 'math-analysis' })}>Math I</button><button className="focus-ring rounded-lg border border-white/10 px-4 py-2" onClick={() => navigate({ section: 'course', courseId: 'linear-algebra-geometry' })}>Math II</button></div></div>;
switch (destination.section) {
case 'dashboard': return <DashboardView state={state} onNavigate={navigate} />;
case 'course': return <CourseHubView courseId={destination.courseId!} state={state} onNavigate={navigate} />;
case 'lesson': return <LessonWorkspaceView courseId={destination.courseId!} topicId={destination.topicId!} state={state} onNavigate={navigate} onComplete={(topicId) => updateState(markTopicComplete(state, topicId))} onQuizComplete={handleQuiz} />;
case 'math-lab': return <MathLabView initialLab={destination.labId} presetId={destination.topicId} />;
case 'practice': return <PracticeView onComplete={handleQuiz} />;
case 'exams': return <ExamsView onComplete={handleQuiz} />;
case 'progress': return <ProgressView state={state} />;
case 'formulas': return <FormulaWorkspaceView state={state} onSave={(formula) => updateState(saveFormula(state, formula))} onRemove={(id) => updateState(removeFormula(state, id))} onNote={(id, note) => updateState(saveFormulaNote(state, id, note))} />;
case 'assistant': return <AIChatView context={destination} onNavigate={navigate} />;
}
};

return <div className="min-h-screen bg-[#07111f] text-[#f4f1e8]"><AnimatedBackground /><Sidebar destination={destination} onNavigate={navigate} isOpen={menuOpen} onClose={() => setMenuOpen(false)} /><header className="sticky top-0 z-30 flex h-14 items-center border-b border-white/8 bg-[#07111f]/90 px-4 backdrop-blur md:hidden"><button className="focus-ring rounded-lg p-2" onClick={() => setMenuOpen(true)} aria-label="Open navigation"><Menu /></button><span className="ml-3 font-semibold text-white">Math-CS</span></header><main className="min-h-screen px-4 py-6 pb-24 md:ml-72 md:px-8 md:py-8 md:pb-10">{renderContent()}</main><BottomNavigation destination={destination} onNavigate={navigate} /></div>;
};
export default App;
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,33 @@
# Temporary migration probe
# Inmerson Math-CS

**Interactive Mathematics for Computer Science**

Inmerson Math-CS is a Computational Notebook-style learning workspace for the first two mathematics courses in the PJATK Computer Science sequence:

- **Math I — Mathematical Analysis / Analiza matematyczna:** functions, sequences, limits, continuity, derivatives, applications of derivatives, integrals, and Taylor polynomials and series.
- **Math II — Linear Algebra & Geometry / Algebra liniowa i geometria:** vectors, matrices, linear systems, determinants, inverses, vector spaces, linear transformations, eigenvalues and eigenvectors, and analytic geometry in two and three dimensions.

## Learning experience

Each topic follows **Learn → Visualize → Practice → CS Connection → Quiz**. Function Explorer, Matrix Lab, and Vector & Geometry Lab use bounded curriculum presets and deterministic calculations. Progress, quiz results, saved formulas, and notes remain local to the device under versioned `math-cs:v1:` storage keys.

## Development

```bash
npm ci --include=optional
npm run dev
```

Run the complete quality gate:

```bash
npm run verify
```

Build the GitHub Pages output under `site/`:

```bash
npm run build:pages
```

The application uses React, TypeScript, Vite, Vitest, Testing Library, Tailwind CSS, Framer Motion, KaTeX, Lucide, and Capacitor.
101 changes: 101 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore

# Built application files
*.apk
*.aar
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/

# Android Profiling
*.hprof

# Cordova plugins for Capacitor
capacitor-cordova-android-plugins

# Copied web assets
app/src/main/assets/public

# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
Loading
Loading