Describe an activity → it becomes a draft → create → review and edit → approve. The activity opens as a readable document with chat beside it. Incomplete work saves itself for later; ask for changes or edit the content directly, then approve the saved revision as a frozen snapshot for assignment.
The product specification defines the product, the chat design its AI execution and the architecture the code; deploy frontend and backend together.
First complete local setup or use a published app.
Parent and child use separate browsers/devices. For a child on another device,
use the published app's HTTPS address, not localhost.
- Sign in with the parent account created during setup.
- Open
/childrenand add a child profile; grade and age are optional. - Describe an activity at
/activities/new, review the settings and confirm any supplied text. Every change saves itself as a draft, without AI; יצירת הפעילות creates the content in one operation. Read it, then use chat for changes or עריכה for manual edits. אישור הפעילות freezes the reviewed revision; open its preview to select a child and assign it. Resume drafts or open approved activities from/activities. - At
/children, select that child, name the device and create an activation code. Give the child the displayed activation address and code; the code works once and expires after ten minutes. /assignmentsopens on open work, which includes submissions waiting for your grading; an open result page shows a submission by itself. Grade any pending short-text answers and finalize the review to show the final total.
- Open the activation address (
/child/activate) in the separate browser and enter the code. No child email or password is needed. - Open an assigned activity from
/childand answer the questions. Use שמירה before leaving; reopening resumes the last saved answers. - Choose הגשה להורה when finished. Submission locks the answers and shows a receipt; the final total appears once any parent review is complete.
Access lasts up to 30 days from activation. Disconnect, expiry, revocation or
cleared cookies require a new code. Parents manage access at /children;
disabling a profile revokes all its devices and pending codes, and re-enabling
requires fresh activation. See the child-flow contracts
for scoring, profile details and history-preserving cleanup.
The installed app remembers its last successful parent or child entry. When child access expires, enter a fresh code inside the app. If all browser data was cleared, choose כניסה לפעילויות on the app's opening screen.
Use the .NET 8 SDK (selected by global.json) and a Node version allowed by
engines.node in package.json; CI uses Node 24.
./scripts/create-parent.sh # First run only
./scripts/configure-ai.sh # Stores your OpenRouter key; makes no paid call
./scripts/dev.shOpen http://localhost:4200. Parent passwords need 12–256 characters with upper and lowercase letters, a number and a symbol. Each provisioned parent gets a family; there is no default account or public registration.
dev.sh installs missing client dependencies, runs the API
and client reload watchers and starts the evaluation dashboard at
http://127.0.0.1:5180, without AI calls. Ctrl+C stops all three; restart
after configuration changes, and run npm --prefix frontend ci after dependency
changes.
Angular proxies /api to http://localhost:5124; use localhost consistently
for cookies. Development applies the checked-in migrations. The development
server listens only on this computer; for other devices, use a
published app.
AI settings, evaluation, results and costs are in the AI guide.
Stop the development watchers, then run either command from the repository root:
./scripts/updatedepsadvanced.sh # Server, tests, evaluation tool and UI
npm --prefix frontend run deps:update # UI onlyThe server script restores the pinned local dotnet-outdated tool and updates
NuGet packages to stable releases. ASP.NET Core and EF Core stay within their
current major versions; other packages may receive major upgrades. It preserves
the target frameworks, regenerates packages.lock.json files and builds the
solution, then runs the UI updater. The UI script uses npm-check-updates --peer
to select the latest peer-compatible versions, removes .angular, node_modules
and package-lock.json, then runs npm install to regenerate the lockfile.
Both commands require network access and leave changes in place if a later step
fails. Review manifests and lockfiles together, resolve any breaking changes,
and run ./scripts/verify.sh before using the updated dependencies.
./scripts/verify.shIt checks locked restores, .NET builds, tests and XML docs, formatting, Markdown, shell syntax, TypeScript, the dashboard tests, Angular tests and the production build. No AI key is needed. For the isolated browser workflow:
./scripts/publish.sh
cd frontend
npx playwright install chromium # Linux CI: add --with-deps
npm run e2eThese tests use disposable data and a local AI provider to exercise the
published app's full activity lifecycle, recovery, conflicts, keyboard/RTL
behavior and 360px/200% text; architecture maps
the suites. Read the comment rules and
UI guide before editing; open FamilyLearning.sln for
backend work.
Serilog writes console output and structured JSON Lines to logs/server-YYYYMMDD.jsonl
at the repository root. Outside the repository, relative paths use the deployed
application's content root. Logs stay outside wwwroot and are ignored by Git.
The workspace configures Log Viewer (berublan.vscode-log-viewer) to follow
logs/server-*.jsonl, including rolled files; open Family Learning API in
its Watches view. dev.sh deletes logs/ when it starts, so each
run begins with clean logs. The file sink runs in shared mode, so management
commands and other local instances append whole entries to the same file;
automated tests write logs under their own temporary storage.
Configure levels, output, rolling and retention in
backend/FamilyLearning.Api/appsettings.json
under Serilog. Files roll daily or at 20 MiB, retaining the latest 14 files
(not necessarily 14 days). To use persistent deployment storage, override
Serilog__WriteTo__File__Args__configure__0__Args__path with an absolute filename
pattern such as /var/log/family-learning/server-.jsonl. Restart after sink changes;
existing minimum-level settings support configuration reload.
Failures before the host logger is created are reported to the console.
Successful API writes and generation outcomes are Information; HTTP 4xx responses,
recoverable AI call failures and interrupted generation are Warning. HTTP 5xx
responses and unexpected failures are Error; host termination is Fatal. Successful
GET/HEAD requests (including health checks and operation polling) and static assets
are Debug-only. Requests carry RequestId matching the ProblemDetails traceId;
trace/span IDs and scoped OperationId, DraftId and Stage connect related
events. Logs exclude request bodies, query strings, credentials and learning
content; keep deployment access restricted.
In JSON, @m contains the rendered message for quick reading; structured values
remain separate properties and @i identifies the message template. @t is UTC,
and an omitted @l means Information.
File writes use a bounded 10,000-event background queue, flushed on orderly shutdown. If full, new events are dropped to preserve request throughput; sink failures and drops appear on standard error. Abrupt process termination can lose queued events. These are operational logs, not an audit ledger.
Library removal deletes unassigned snapshots and archives assigned ones. Under ניהול נתונים, מחיקת כל הפעילויות deletes every activity with the work assigned in it, and מחיקת נתוני הילדים deletes profiles, device access and assigned work; both keep parent accounts and AI configuration. See retention and reset for the full deletion contract.
Development stores SQLite and Data Protection keys in the ignored
backend/FamilyLearning.Api/data/. Set Storage__Directory to an absolute path
to relocate them, using the same path for provisioning, migrations and runtime,
and keep keys with the database so cookies survive restarts.
A database from before the activity-only redesign (9 October 2026) refuses to
migrate while it holds learning records. With the app and watchers stopped,
dotnet artifacts/app/FamilyLearning.Api.dll --activity-only-cutover clears all
learning records and child access in one transaction, keeping parent accounts,
families, keys and AI configuration, then migrates. It starts no server, worker
or AI call, and once applied repeating it changes nothing.
For model changes, stop dev.sh first, since its watcher can apply an
unfinished migration:
dotnet tool restore
dotnet ef migrations add YourChange --project backend/FamilyLearning.Api \
--output-dir Infrastructure/Persistence/Migrations
dotnet ef migrations has-pending-model-changes --project backend/FamilyLearning.ApiNever edit an applied migration; add a new one. Review migrations before
applying them, since they can remove learning data that a rollback cannot
restore. Back up with SQLite's backup API or VACUUM INTO, not by copying an
active database file.
./scripts/publish.shartifacts/app/ holds the API and client and needs the ASP.NET Core 8 runtime;
publishing preserves artifacts/app/data/. Deploy with Production, HTTPS
and one API process. Keep the database and keys in the same persistent directory
across deployments, outside wwwroot. From the repository root, apply migrations
before starting; Production does not migrate automatically:
export Storage__Directory="$HOME/.local/share/family-learning"
dotnet artifacts/app/FamilyLearning.Api.dll --migrateTo use an existing database, set Storage__Directory to its directory instead;
stop its other API process first. For a new database, provision a parent with
dotnet artifacts/app/FamilyLearning.Api.dll --create-parent you@example.com
in the same terminal. Supply Ai__ApiKey or OPENROUTER_API_KEY to enable
generation; Production uses the published AI profile
and environment variables, not development user secrets.
The host processes X-Forwarded-For and X-Forwarded-Proto from one loopback
proxy before HTTPS redirection, authentication and rate limiting. Preserve the
original Host header and disable proxy buffering for /api/library/changes
and /api/child/changes.
When a hosting platform's proxy is the only route to the process, set
ForwardedHeaders__ClientIpHeader to the header in which that proxy sends the
client address. The host then accepts it and X-Forwarded-Proto from any
connecting address, so never set it on a directly reachable server. For another
remote proxy, configure its trusted address in ForwardedHeadersOptions,
following
Microsoft's proxy guidance.
/health reports API process availability, not database or AI readiness. The
PWA service worker is enabled only in published builds; the development server
serves live changes.
The worker prefetches HTML, styles and the main entry script. Shared and route chunks are cached when requested; unvisited screens need a connection on first use. Private API data stays uncached.
Published scripts, styles, fonts and images with Angular's content hash in their
filename use Cache-Control: public, max-age=31536000, immutable. HTML (including
page fallbacks), service-worker control files and other stable filenames use
no-cache to revalidate with the server. API responses retain no-store.
The Dockerfile builds the same package as an image for any container host. It listens on port 8080 and applies pending migrations before each start. Run one instance with persistent storage and set:
Storage__Directory: the persistent mount, such as/data.OPENROUTER_API_KEY: the AI key, stored as a host secret.ForwardedHeaders__ClientIpHeader: the platform proxy's client-address header.Serilog__WriteTo__File__Args__configure__0__Args__path(optional): a log file pattern on persistent storage, such as/data/logs/server-.jsonl.
Provision the first parent from a shell in the running container, which inherits these settings:
cd /app && dotnet FamilyLearning.Api.dll --create-parent you@example.comfly.toml runs the image on one always-on machine in Frankfurt with
a 1 GB volume at /data, logs on that volume and Fly's Fly-Client-IP header.
Install flyctl and sign in, then deploy
from the repository root:
fly apps create family-learning # If taken, choose another and update fly.toml
fly volumes create data --region fra --size 1 -y
read -rsp 'OpenRouter API key: ' key; echo
printf 'OPENROUTER_API_KEY=%s\n' "$key" | fly secrets import; unset key
fly deploy --ha=false
fly ssh console # Then provision the parent as in ContainerOpen https://<app>.fly.dev. Later releases need only fly deploy; fly logs
streams console output. Fly snapshots the volume daily and keeps snapshots for
five days. To use another host, delete fly.toml and follow Container.
Cost estimates must use Frankfurt pricing. As checked on 10 October 2026,
Fly's pricing table lists $3.69/month for a
shared-cpu-1x machine with 512 MB in Ashburn/Secaucus, assuming 30 days of
continuous runtime. That is not a Frankfurt quote or the total app bill.
Add storage, outbound data and AI usage.
Keeping the same machine size, count and runtime keeps compute cost unchanged; more requests can still increase bandwidth and AI charges or require more capacity. At Europe's listed $0.02/GB, one million responses of 100 KB each transfer about 100 GB ($2); at 1 MB each, about 1,000 GB ($20), before AI costs. These examples use decimal units and bytes actually transferred after compression; browser cache hits avoid those transfers. Use Fly's invoice for actual charges rather than treating these estimates as a fixed monthly price.