The portfolio that documents itself
How work on your project would actually happen — written down, dated and walkable.
For a founder — cost, outcome, time
- Sprints
- 30
- Memory files
- 47
- Rules promoted
- 13
- Ramp-up saved
- ~94 min
A language model doesn’t remember. Every session starts blank, so multi-session work either burns the first half hour re-explaining, or needs a memory around the model.
A site that is its own proof: every decision dated, every rule earned, all of it public.
- Next.js 16
- React 19
- TypeScript
- Tailwind v4
- Claude Opus
- Framer Motion
In short
This portfolio ships as its own case study, listed first on the home page. The five client cases (Noble, Angel, Lexora, Fieldmark, smm-factory) describe work I did for others. This one describes the machine — the memory files, classifier, and promotion discipline — that produced all six, in the open, on the exact site you are reading.
Written for fractional clients evaluating whether the workflow scales to their engagement. If you're screening for craft, start with Noble. If you're screening for how work happens, start here.
hrekov.dev is a Next.js 16 portfolio whose case study is itself — the memory files, DEVLOG entries, and promotion discipline that produced the site are public and walkable in a single repo.
The site is a byproduct. The actual artifact is the workflow that produced it.
1. Cold open
The five client case studies on this site describe work I did for others. This one describes the site you're reading.
That sounds self-obsessed. It would be, if the site were the point. It isn't — the site is a byproduct. The actual artifact is the workflow that produced it, and the workflow only holds up if I can show it working on something. This portfolio was the something.
Forty-seven memory files. Eighty journal entries. Thirteen of them promoted to durable rules. Thirty sprints across thirty-three calendar days, solo. Every decision is on disk, timestamped, in a public repo you can clone. Half the interesting ones are wrong on the first pass — the discipline is that the second pass catches them before they compound.
If you're evaluating whether AI-augmented delivery scales to a real engagement, don't read a whitepaper. Read the machine that made itself.
Part 1 of 3 · 8:15 · The system — four types, one index, the discipline that produces memory. Two AI avatars, one HeyGen render. Face, voice, edit — all generated. That's the point.
2. The stateless-model problem
A language model doesn't remember. Every session opens with the same blank slate: no prior context, no dated decisions, no record of which questions have already been asked and answered. Demos rarely expose this because a demo lives inside one conversation. Products don't have that luxury.
Multi-session work forces a choice. You can re-hydrate context by hand every session — quote the last decision, restate the constraint, warn about the same three dead-ends — and burn the first thirty minutes on setup. Or you can build a persistence layer around the model: durable notes the next session reads before saying anything.
Every serious agentic project ends up doing the second. The interesting question isn't whether to build persistence — it's what shape the notes take, and how you keep them from becoming a second junk drawer larger than the codebase. That's what the rest of this case study is about.
3. The persistence layer
The workflow lives in one directory:
~/.claude/projects/-Users-ruslan-portfolio/memory/. Twenty-eight Markdown
files at time of writing, each with YAML frontmatter, each authored by
Claude at the exact moment a durable fact crystallized mid-conversation.
MEMORY.md
brainstorm_discovery_domain_expert_agent_2026-10-03.md
case_study_template.md
feedback_auto_open_new_files.md
feedback_css_cascade_first.md
feedback_hero_content_placement.md
feedback_mdx_blockjs_false.md
feedback_prefer_written_source_over_recall.md
feedback_trust_but_verify_tooling.md
feedback_visual_direction.md
iteration-11.md
iteration-14.md
iteration-7.md
iteration-9.md
iteration-expert-v1.md
portfolio_active.md
portfolio_ai_video_pivot.md
portfolio_content_strategy.md
portfolio_design_pivot.md
portfolio_font_pivot.md
portfolio_podcast_format.md
portfolio_voice_cloning.md
portfolio_workflow.md
project_heygen_clone.md
project_video_pilot_pause_2026-09-27.md
reference_elevenlabs_video_pricing.md
reference_linkedin_api_endpoint.md
shiploop_discipline.md
user_name_romanization.md
Four types, deliberately narrow. Every entry belongs to exactly one — ad-hoc notes drift into a wiki nobody reads.
user
Who Ruslan is — surname romanization, technical background, preferences. Written once, rarely edited. Frames how the model speaks.
feedback
Corrections and validated approaches. Every rule carries a Why line so future sessions can judge edge cases instead of following blindly. Includes the CSS cascade rule that ended three hours of Tailwind-under-Turbopack debugging.
project
Ongoing initiatives, decisions, dates. Fastest-moving type; entries decay in weeks and get rewritten in place.
reference
Pointers to external systems. Never the data itself, just where to look.
Each file has a minimum shape:
---
name: <short title>
description: <one-line hook — used at recall time to gauge relevance>
type: user | feedback | project | reference
---
The description matters more than the body. On a fresh conversation
the model reads MEMORY.md — an index of one-line pointers — and only
opens the files whose descriptions look load-bearing to the current
task. Bad descriptions = wasted context; good ones = the machine
remembering the right thing without being asked.
The featured artifact is feedback_css_cascade_first.md — one rule, one
incident, one clause on how to apply it. Reproduced verbatim below.
---
name: CSS cascade before JIT/HMR blame
description: When "the same Tailwind class works on one element but not another", diagnose CSS layer/cascade first — never assume JIT ordering or HMR cache
type: feedback
---
**Rule.** When a Tailwind utility class visibly works on one element but
not another (especially: `<button>` works, `<a>` doesn't), the bug is
almost never in Tailwind's JIT compiler or Turbopack's HMR cache. It is
a CSS **cascade** problem. Look at:
1. Are there unlayered rules in `globals.css` targeting the failing
element? (Element-level rules like `a { color: inherit }` outside
`@layer base` beat every layered utility regardless of specificity —
Tailwind v4 puts utilities inside `@layer utilities`.)
2. Is inheritance in play? (Anchors inherit `color` from parent by
default; buttons don't. That asymmetry explains why the "same class"
yields different computed values.)
3. Read the CSS bundle: `curl /_next/static/chunks/[root-of-the-server]__*.css`
and grep for the actual rule. If the rule *is* present in the bundle
and still doesn't win, it's cascade, not compilation.
**Why:** In the 2026-09-25 portfolio session I spent three separate
edits inline-styling CTA pills to work around what I diagnosed as a
"Tailwind JIT quirk under Turbopack HMR". The real cause was
`a { color: inherit }` sitting unlayered in `globals.css`, beating
every `.text-[color:var(--cta-ink)]` utility on every anchor CTA. The
tag difference (button vs anchor) was the diagnostic tell I ignored
three times before checking layers.
**How to apply:**
- The moment computed color/background differs from what the utility
class prescribes AND the same class works elsewhere with a different
tag, jump straight to inspecting `@layer` structure in `globals.css`.
- Never reach for `style={{ color: "var(--x)" }}` as a "quick fix" for
a Tailwind class not applying — it hides a cascade bug that will
affect other elements the same way.
- On this project the rule is documented in `STABLE_LOGIC.md` under
"CSS authorship (Tailwind v4)"; if you break it, that file will call
it out.
That is one file out of thirty. Every one of the others reads like this: rule at the top, incident that produced it in the middle, edge case guidance at the bottom. No lore, no essays, no aspirational principles — just what the machine needs to not repeat yesterday's mistake.
4. The MEMORY.md index
Thirty files is already too many to open blindly at the start of a
session. Reading them all would eat context before the actual work
started. So the memory system carries its own directory: MEMORY.md,
a flat list of one-line pointers.
- [User's surname: Hrekov (not Grekov)](user_name_romanization.md) — Ukrainian romanization Г→H. Full name "Ruslan Hrekov". Gmail keeps the old spelling — do not "fix" the email.
- [ShipLoop is a discipline, not an event](shiploop_discipline.md) — classify every request; nothing gets built without spec+plan+tasks; how the user invokes ShipLoop.
- [Portfolio shiploop active](portfolio_active.md) — Sprint 0 bootstrap shipped 2026-09-20; retrofit ShipLoop discipline retroactively.
- [Portfolio workflow — wait for shiploop](portfolio_workflow.md) — superseded but kept for historical context.
- [Portfolio design pivot (2026-09-23)](portfolio_design_pivot.md) — dark Lusion → minimalist editorial. Monochrome + green dot. Reference: Nizar Ali Dribbble 24766210.
- [Visual direction — reference-first](feedback_visual_direction.md) — never write a design brief from abstract description alone; get concrete references from Ruslan first.
- [Portfolio Sprint 7 shipped](iteration-7.md) — redesign done 2026-09-23; do not reintroduce dark/three.js/gsap without new spec; copy softening held for user sign-off.
- [Portfolio font pivot (2026-09-24)](portfolio_font_pivot.md) — Playfair Display upright + strictly monochrome cursor-reactive DotGrid w/ idle breathing wave, all tunables in top-of-file CONFIG.
- [Portfolio Sprint 9 shipped](iteration-9.md) — single-page → multi-page IA. Global chrome in root layout; Nav route-based w/ prefix match.
- [CSS cascade before JIT/HMR blame](feedback_css_cascade_first.md) — same Tailwind class works on `<button>` but not `<a>`? It's `@layer` cascade, not compilation. Never inline-style-around a utility that "isn't applying".
Every line has the same shape: - [<title>](<file>) — <one-line hook>.
Nothing longer, nothing hierarchical. The model reads the index in one
pass, ranks which files look load-bearing to the current task, and
opens only those. In practice, a typical session opens two or three
files, not thirty. The index is what makes the collection scale.
5. The discipline that produces memory
Memory files don't write themselves. Something has to route each incoming message to the right pipeline: a quick question gets an answer, a bug gets a diagnose-fix-verify-log, a large feature gets a full spec+plan+tasks triple before a line of code moves. That router is ShipLoop — a nine-class classifier applied silently to every prompt (Q, B, S, L, R, T, D, M, O), each with its own model tier and its own required artifact.
ShipLoop · classifier routes each prompt to a model tier, lifecycle drops a memory file at every phase
Row 1 · nine classes resolve to three tiers, silently, before any work happens
CLAUDE.md · ShipLoop Protocol
- Person / role
- Process step
- Decision / gate
- Flow
Read as steps (4)
- 01Message → Classify silently
- 02Classify silently → Haiku tier — quick
- 03Classify silently → Sonnet tier — scoped
- 04Classify silently → Opus tier — new scope
Row 2 · lifecycle · nothing skips a phase, every phase ends with a memory file on disk
Discovery → Retro, every pass
- Process step
- Decision / gate
- Outcome
- Flow
- Key path / outcome
Read as steps (4)
- 01Discovery → Spec + Plan + Tasks
- 02Spec + Plan + Tasks → Build
- 03Build → QA
- 04QA → Ship (✓ approved)
Lesson: The classifier is one half of the discipline; the lifecycle is the other. Together they turn drift-prone generative work into something that compounds.
The model-routing rules (which class runs on Haiku, Sonnet, or Opus, and why) live on the About page's stack section. I won't duplicate them here; the point of the routing rules is that they're one lookup away, not restated everywhere.
6. DEVLOG → STABLE_LOGIC — the 16.3% number
Eighty entries in DEVLOG.md. Thirteen of them promoted to
STABLE_LOGIC.md. That's 16.3%. The other 83.7% stayed in the journal.
Journaling and learning are not the same thing. A journal captures what happened; that's cheap and worth doing. But most of what happened is context: the shape of an incident, the sequence of dead ends, the exact error text. Useful for the next twenty minutes; noise for the next month. If every DEVLOG entry got treated as a durable lesson, the model would drown in advice — most of it about problems that don't recur.
Promotion has a bar. A rule graduates only after it holds up across at least two sessions or one full sprint. Two-session proof means the same diagnosis worked twice on different symptoms. One-sprint proof means a constraint was written into a decision and no later refactor tried to overturn it. Below that bar, the entry stays a journal note — dated, searchable, cheap to skim, but not loaded on every session.
An example. The 2026-09-25 CSS cascade incident (three edits inline-styling CTA pills, all wrong) started as a single DEVLOG entry titled "invisible CTA text." It graduated after a second session hit the same failure mode on a different element and the rule caught it in the first turn.
The two forms of the same lesson, side by side:
The tag mattered because globals.css had a { color: inherit; } sitting unlayered. Tailwind v4 emits utility rules inside @layer utilities, and any unlayered rule beats every layered one regardless of specificity. Anchors inherited --ink-body from body; buttons don't inherit color, so they looked fine.
Every element-level rule in app/globals.css lives inside @layer base { ... }. Never author html, body, a, ::selection etc. unlayered. Reason: Tailwind v4's layer order (theme, base, components, utilities) means unlayered rules always win. Regression: anchor-based CTA pills rendering dark-on-dark — looks like a JIT bug, never is.
The DEVLOG entry is a story. The STABLE_LOGIC entry is a rule. Journaling is the raw material. Promotion is the discipline.
How a DEVLOG entry becomes a STABLE_LOGIC rule
- Session · person / role
- Entry / outcome · data / signal
- Promotion gate · decision / gate
- Outcome
- Flow
- Key path / outcome
- Async / optional
Read as steps (5)
- 01Session → Work complete
- 02Work complete → DEVLOG entry
- 03DEVLOG entry → Survived re-test?
- 04Survived re-test? → STABLE_LOGIC rule (✓ approved) — proven
- 05Survived re-test? → Stays in journal — unproven (async / optional)
Part 2 of 3 · 8:01 · The war stories — three hours blamed on Turbopack for a one-line CSS cascade bug, and a one-day dark-Lusion build killed by a single Dribbble shot.
7. What the machine produced
Sixty-plus classified tasks. Five case studies drafted, one shipped end-to-end (Noble is the flagship), four in review. Four of the shipped surfaces, each with its own spec triple or DEVLOG entry naming what broke and what held:
Monochrome dot-grid canvas
Cursor-reactive `<canvas>` over the warm-paper page. Every tunable (pitch, dot size, falloff radius, chaos mix) lives in a top-of-file `CONFIG` object. Locked in `portfolio_font_pivot.md`.
Single-page → multi-page IA
Sprint 9 restructure. Global chrome in root layout, Nav route-based with prefix match. Journal between Work and About once the blog shipped.
L2 / L3 case-study toggle
Executive vs technical view. `TechnicalDetail` passages CSS-hidden in L2 via a `[data-view]` flip, localStorage-persisted so recruiters stay in exec mode.
EU-gated GA4 consent
`proxy.ts` reads `x-vercel-ip-country`, sets `geo-eu` cookie. Banner shown only in the 31 EU/EEA/CH countries. Non-EU visitors see no banner, no cookie prompt.
The reflex says discipline slows delivery. The measurement disagrees.
Every spec triple I wrote saved more time on the next session than it
cost to author, because the next session started with the constraint
already stated instead of re-derived from a diff. Every DEVLOG entry
that graduated to STABLE_LOGIC.md prevented a class of question from
recurring — the promotion is what compounds, not the journaling.
The cadence held because the failure mode of AI-assisted work isn't speed; it's drift. Same session, three different answers to the same question. Different session, the fix from yesterday reintroduced as a "clever" refactor. The memory files are what stopped that. The classifier decides which channel a request goes down; the memory decides what the model already knows before it opens its mouth. Two small primitives, one compounding result.
The failure mode of AI-assisted work isn't speed; it's drift. The memory files are what stopped that.
8. Context re-hydration saved per session
A fresh session with no memory has to be told, or has to re-derive from a diff, the exact set of decisions already made. Four of the most expensive questions, each resolved by a single file in one turn:
Dark or light? Exact hex values?
Pivoted to warm paper on 2026-09-23 — `#F5F4EF` page, `#111` ink, `#22C55E` the only chromatic accent (availability dot). Answered by `portfolio_design_pivot.md`.
Tailwind utility fails on `<a>` — JIT bug?
No — it's `@layer` cascade. Unlayered element rules in `globals.css` beat every layered utility. Answered by `feedback_css_cascade_first.md` before another hour sinks in.
Display font — italic Playfair?
Pivoted out on 2026-09-24; display is upright Playfair, body is Inter, no third family. Answered by `portfolio_font_pivot.md`.
Still shipping the ElevenLabs voice clone?
No — pivoted to fully AI-generated video on 2026-09-27 after HeyGen two-camera render landed at 30 credits. Answered by `portfolio_ai_video_pivot.md`.
Rough estimate: two minutes per file × thirty files ≈ sixty minutes of ramp-up the model doesn't have to spend rebuilding context before it can act. That number is soft, but the direction isn't.
9. How to steal it
The specific memory files here are project-shaped and won't fit yours. The pattern is generalizable. Four portable primitives — each one small enough to adopt in an afternoon, strict enough that the system doesn't drift into a second junk drawer:
Four-type taxonomy
Every entry belongs to exactly one of **user**, **feedback**, **project**, **reference**. Types give the system a shape; ad-hoc notes devolve into a wiki nobody reads.
Three-field frontmatter
`name`, `description`, `type` — nothing more required. The `description` is what the model uses at recall time; write it for the future you who has forgotten this exists.
The Why line
Every feedback rule carries a `Why:` line naming the incident that produced it. Rules without reasons don't survive edge cases; they get followed blindly or overturned on a whim.
Flat MEMORY.md index
A one-line pointer per file, nothing hierarchical, kept under 200 lines. If it grows past that, the pointers were too verbose or the files too fragmented.
The actual auto-memory spec these files implement ships inside Claude Code itself. The relevant excerpt — types and when-to-save triggers — is reproduced below, attribution intact.
Auto-memory spec — verbatim excerpt (types + when-to-save)technical
Excerpt from Claude Code's system-embedded auto-memory spec — not authored by Ruslan, reproduced here for context. Slightly abridged.
Types of memory. There are several discrete types of memory that
you can store in your memory system:
- user — Information about the user's role, goals, responsibilities,
and knowledge. Great user memories help you tailor your future
behavior to the user's preferences and perspective. Save when you
learn any details about the user's role, preferences,
responsibilities, or knowledge.
- feedback — Guidance the user has given you about how to approach
work — both what to avoid and what to keep doing. Record from failure
AND success: if you only save corrections, you will avoid past
mistakes but drift away from approaches the user has already
validated, and may grow overly cautious. Lead with the rule itself,
then a Why: line (the reason the user gave — often a past incident
or strong preference) and a How to apply: line (when/where this
guidance kicks in). Knowing why lets you judge edge cases instead
of blindly following the rule.
- project — Information about ongoing work, goals, initiatives, bugs,
or incidents within the project that is not otherwise derivable
from the code or git history. Save when you learn who is doing what,
why, or by when. These states change relatively quickly so try to
keep your understanding of this up to date.
- reference — Pointers to where information can be found in external
systems. These memories allow you to remember where to look to find
up-to-date information outside of the project directory.
What NOT to save. Code patterns, conventions, architecture, file
paths, or project structure — these can be derived by reading the
current project state. Git history, recent changes, or
who-changed-what — git log / git blame are authoritative. Debugging
solutions or fix recipes — the fix is in the code; the commit message
has the context. Anything already documented in CLAUDE.md files.
Ephemeral task details.
Part 3 of 3 · 8:07 · How this podcast was made — the pricing estimate that was off by 3×, the architecture that solved a non-problem, and how to steal the whole discipline starting with one file.
10. The recursive close
You're browsing the artifact.
The dot-grid canvas reacting to your cursor is Sprint 7 — a strictly
monochrome cursor-only DotGrid with every tunable in a CONFIG object
at the top of DotGrid.tsx, all locked in portfolio_font_pivot.md.
The Playfair Display headlines are the 2026-09-24 pivot away from
italic display. The route you're on (/work/hrekov-dev) exists because
Sprint 9 restructured the site from single-page to multi-page. If a
consent banner appeared at the bottom, you're in the EU, and Sprint 11
put it there via proxy.ts.
Every visible surface has a memory file behind it. This page is one more.
Receipts
How it was made
Token scorecard
- Complexity
- L
- Duration
- 27 days active
- Tier
- 2 · ShipLoop
Model mix
- Opus
- 100%
Build timeline
28 days from Sep 20 to Oct 17, 66.0M tokens, 151 commits
- Phases
- 4
- Days
- 28
- Peak day
- 7.3M
- Peak commits
- 36
Artifacts
- costDEVLOG → STABLE_LOGIC promotion rate
13 rules graduated from 80 journal entries · 16.3%
- timelineCadence to date
30 sprints across 33 calendar days · ~0.9 sprints/day
- timelineRamp-up saved per new session
~94 min · 47 memory files × ~2 min context re-hydration each
Process log
- Font pivot to Playfair Display + ambient dot wave
One decision, three memory files created (font, canvas, gate). Later sessions never re-litigated.
- Sprint 9 · Site restructure — single-page → multi-page IA
Spec triple caught the routing-boundary decision before the first component moved.
- Sprint 11 — Blog foundation + case study L2/L3 + GA4
Next.js 16 broke on middleware.ts; the memory of that break kept future sessions off the old name.
- CSS layer bug — real root cause of "invisible CTA text"
One debug hour became one memory file. Every Tailwind-on-anchor question since has been a one-turn answer.
Want this workflow on your project?
Send one paragraph about it. I reply within two business days with a scope and a fixed price.