# matlog > A local-only BJJ training journal that runs entirely in the browser (a static site on GitHub Pages: no account, no server). The log lives in the browser's IndexedDB, with a copy in localStorage, so an agent can only read or change it by driving a browser with the app open. App: https://parmsam.github.io/matlog/ Source: https://github.com/parmsam/matlog ## Link actions Open the app with a `do` query parameter to log something. The parameters are removed from the URL once it has run, and the app shows what it did with an Undo button. Settings never travel in the URL. - `?do=log&type=open-mat&min=60`: a finished 60-minute open mat that ended now. `type` is class, open-mat, drilling, comp or roll (aliases: lesson, openmat, drill, competition, tournament, round, spar, sparring) - `?do=start&type=class` / `?do=stop&type=class` / `?do=toggle&type=class`: the timer. One session runs at a time (starting another switches it); a round (`type=roll`) can run inside a session. Stopping a session stops its round - `&min=90`: length in minutes (1–600). With a length, start and toggle log a finished entry instead - `&ago=15`: it ended (log) or started (start) 15 minutes ago (0–527040) - `&gi=gi|nogi`, `&tech=knee cut,armbar from guard` (techniques, comma-separated; unknown names are kept as typed) - Rounds only (a round belongs to whatever session it happens in: class, open mat, drilling or comp; one logged while a session runs is linked to it): `&partner=Sam&belt=white|blue|purple|brown|black&size=smaller|similar|bigger&sub=armbar,rnc&tapped=triangle&stuck=half guard&outcome=win|loss|draw` - Moves in a round: `&landed=knee cut,scissor sweep` (you landed them) and `&tried=berimbolo` (you went for it, it didn't land) - Positional rounds: `&from=side control&role=bottom|top` - Situational rounds: `&goal=escape or sweep&got=2&theygot=1&went=framed early and got out` (got and theygot count how many times each side got their goal) - Sessions: `&enjoy=1..5&plan=yes|partly|no` (a check-in) - Study (not mat time): `?do=log&type=study&min=10&source=Half guard series` - `¬e=…` on anything - Classes start on the hour by default (a setting): a class logged after the fact starts on the hour at or before; a live one started within 10 minutes after the hour starts on it Examples: `?do=log&type=class&min=90&gi=nogi&tech=knee cut`, `?do=toggle&type=roll`, `?do=log&type=roll&partner=Jo&belt=blue&sub=rnc`. On iPhone, links open in Safari, which keeps its own data, separate from the Home Screen app. ## Scripting API (in the page) When you control the page (Playwright `page.evaluate`, DevTools, a browser agent), use `window.matlog`. Calls are synchronous, return plain JSON-safe data, and throw an Error with a readable message on bad input. Names and options match the link actions. - `matlog.help()`: this reference as text - `matlog.state()`: `{ name, now, rank: { belt, stripes }, promotions, matHours, sessions, running: [{ type, since, minutes }], workingOn, last7Days: { sessions, minutes, rounds, subsFor, subsAgainst, techniques } }` - `matlog.log(type, opts?)`, `matlog.start(type, opts?)`, `matlog.stop(type, opts?)`, `matlog.toggle(type, opts?)`: opts are `{ min, minutesAgo, gi, techniques, partner, belt, size, subs, tapped, landed, tried, stuck, outcome, from, role, goal, got, theyGot, howItWent, note }` (lists as an array or comma text). Returns `{ result: 'logged' | 'started' | 'stopped' | 'switched', entry }` or `{ result: 'nothing-to-do', reason }` - `matlog.entries(days = 7)`: sessions, rounds and promotions from the last N days - `matlog.undo()`: undo the last change made in the app (within 5 minutes) - `matlog.markdown(days = 7)`: the last N days as Markdown (the same format the Paste dialog reads) - `matlog.importMarkdown(text)`: log a class note; returns `{ sessions, rounds, ids }` - `matlog.parseMarkdown(text)`: what importMarkdown would log, without logging - `matlog.stats()`: subs hit and tapped to, ratio, stuck positions, partners, by partner belt, and `situations` (situational rounds by start: rounds, got, theyGot) - `matlog.techniques()`: each technique with sessions drilled, `landed` and `attempts` in rounds, last used, and from → to positions - `matlog.patterns()`: plain descriptions, each with what it's based on - `matlog.focus(text?, opts?)`: focuses, running first, each with `{ text, role, state: 'active'|'over'|'resting'|'done', week, weeks, blocks, ifWorks, ifFails, tracking, thisBlock }`. With text, adds one: opts `{ role: 'main'|'complement'|'habit'|'experiment'|'side', blockDays: 7|14, ifWorks, ifFails, target }`. Only add one when the person asks - `matlog.problems(text?, opts?)`: `{ open, solved }`. With text, logs a problem: opts `{ source: 'internal'|'external'|'predictive', idea }` - `matlog.solve(id)`: mark a problem solved - `matlog.injuries(area?, opts?)`: `{ active, healed, note }`, each `{ id, what, severity, since, days, how, trainingAround, updates, healed }`. With an area (`'left knee'`, `'low back'`), logs one: opts `{ severity: 'minor'|'moderate'|'serious', how, around, daysAgo }` - `matlog.heal(id)`: mark an injury healed - `matlog.techniqueNotes(technique, text?, opts?)`: `{ technique, notes }`; with text, adds a note: opts `{ kind: 'detail'|'observation'|'question'|'source', url }` ## Class note format (v2) Paste this into the app (Paste a note, or `matlog.importMarkdown(text)`); exports use it too. Older forms (comma lists, one-line rounds) still import. ```markdown ## Class - Date: 2026-10-08 18:00 - Length: 90m - Gi: no-gi - Topic: Half guard passing ### Techniques - Knee cut pass - Key detail: underhook the far arm first - Shoulder post wrist grab arm drag ### Rounds - Sam (purple, bigger) - Started from: half guard bottom - Goal: sweep or come up - Got it: 2 - They got it: 1 - How it went: came up twice once I got the underhook - Hit: armbar - Tapped to: triangle - Landed: knee cut - Stuck: half guard ### Problems - Got flattened in half guard - Idea: knee shield first ### Injuries - Left knee (minor) - How: heel hook - Training around it: no leg locks ### Notes Anything else, as free text. ``` - `## Class` starts a session: Class, Open mat, Drilling, Comp or Study. Several sessions can follow each other. - Session details are bullets: Date (YYYY-MM-DD HH:MM), Length (90m, 1h30), Gi (gi or no-gi), Topic, Enjoyed (1–5), Followed my plan (yes, partly, no). Study also takes Source. - `### Techniques`: one technique per bullet. Sub-bullets add notes to it: Key detail, Observation, Question, Source (a link in brackets works). - `### Rounds` (or Rolls, Matches): one partner per bullet, with belt, size and length in brackets. Sub-bullets: Started from (a position, then top or bottom), Hit, Tapped to, Landed, Went for, Stuck, Result (win, draw, loss), Note. Lists are comma-separated. - Situational rounds add Goal (the rule, e.g. sweep or come up), Got it and They got it (how many times each side got their goal; the round can reset and go again), and How it went (a sentence or two). - `### Problems`: one problem per bullet, `(solved)` at the end if it is. Sub-bullet: Idea. - `### Injuries`: one per bullet, like `Left knee (minor)`. Sub-bullets: How, Training around it. - `### Notes`: free text. Anything matlog doesn’t recognise also ends up in the note. ### Turning messy notes into this format To turn a voice memo or quick notes into a class note with a chat assistant, give it the format above and these rules: only include what the person said (no invented rounds, partners, submissions or times); keep their own words for problems, ideas and notes; no advice; leave out the Date line if they didn't say when. The app's Paste dialog has a "Copy prompt for an AI" button that builds this prompt with the person's own technique names. There is also a Claude skill in the repo: `skills/matlog-note/SKILL.md`. ## Common agent recipes - Log a 60 minute open mat I just finished `matlog.log('open-mat', { min: 60 })`, or open `?do=log&type=open-mat&min=60`. - I just bowed in to class `matlog.start('class')`; later `matlog.stop('class')`. - Log a round: tapped Sam (purple) with an armbar, got stuck in half guard `matlog.log('roll', { partner: 'Sam', belt: 'purple', subs: 'armbar', stuck: 'half guard' })`. - At open mat I landed a knee cut twice and went for a berimbolo One round each: `matlog.log('roll', { landed: 'knee cut', tried: 'berimbolo' })`, `matlog.log('roll', { landed: 'knee cut' })`. - I'm working on my knee cut for the next two weeks `matlog.focus('knee cut', { role: 'main', blockDays: 14 })`; later `matlog.focus()[0].thisBlock`. - I keep getting flattened in half guard `matlog.problems('getting flattened in half guard', { source: 'internal' })`; `matlog.solve(id)` once it's solved. - I tweaked my left knee at open mat `matlog.injuries('left knee', { severity: 'minor', how: 'open mat', around: 'no leg locks' })`; `matlog.heal(id)` later. Report what was logged; never give medical advice. - Situational round from side control bottom, escape or sweep: got out twice, got pinned once `matlog.log('roll', { from: 'side control', role: 'bottom', goal: 'escape or sweep', got: 2, theyGot: 1 })`. `matlog.stats().situations` totals them by start. - How many mat hours do I have? `matlog.state().matHours`. - What do I get stuck in, and what do I tap to? `matlog.stats()`, or `matlog.patterns()` for sentences. - Log this class note `matlog.importMarkdown(text)`. ## What the app will not do matlog describes what was logged. It doesn't coach, score, rank you against others or recommend techniques, and agents using it shouldn't present its numbers as advice.