Skip to content

Improve pylings interaction model#2

Closed
abhiksark wants to merge 96 commits into
mainfrom
rustlings-ux
Closed

Improve pylings interaction model#2
abhiksark wants to merge 96 commits into
mainfrom
rustlings-ux

Conversation

@abhiksark

@abhiksark abhiksark commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add first-run topic orientation plus resume state for returning learners
  • Add a Smart Picker, pylings topics, guided output panel, progressive hints, and instant advance behavior
  • Complete the planned roadmap curriculum with seven new specialized topics: itertools, json, datetime, enums, pathlib, oop_advanced, and async
  • Document and test the interaction model and curriculum expansion

Test Plan

  • .venv/bin/python -m pytest -q (91 passed)

Captures the brainstormed decisions: single pylings command, Textual TUI,
watch loop, strict linear gating via '# I AM NOT DONE', info.toml metadata,
single-file exercises with bare asserts, subprocess-based verifier,
pip-installable distribution.
- Add 're-broken content' migration step (existing exercises ship with
  correct answers; placeholders needed for real fix-to-pass UX)
- Add .gitignore migration for /.pylings/ runtime dir
- Define Manifest dataclass that was referenced but never spelled out
- Add formal .pylings/state.json schema (JSON arrays, not Python sets)
- Document --root global CLI flag used by tests
- Clarify n binding: skips success animation, not a redundant 'advance'
- Clarify h binding: hint section is collapsed by default in output panel
- Make welcome_message / final_message optional with defaults
- Expand reset state transitions for X==current, X<current, X>current
Critical:
- Split 'pass' into TUI-pass (exit+marker) and verify-pass (exit only).
  Fixes the contradiction where CI was supposed to run verify green on
  a curriculum that is broken-by-design.
- Pin .pylings/ to <root>/.pylings/ for test isolation under --root.

Hardening:
- Force UTF-8 encoding in subprocess so non-ASCII output doesn't mangle
  on misconfigured-locale systems.
- Reject empty info.toml (zero exercises) instead of silently launching
  an empty TUI.
- Document lazy-import rule for Textual: subcommands must stay sub-100ms
  cold-start; only watch/no-arg pay the TUI import cost.
- Add --version flag (argparse-handled).
- Note pyproject package-data inclusion for pylings.tcss.
- Note solutions/ directory as a known follow-up.

Updated success criterion #5 to match the new verify model, and added
#7 for cold-start latency.
20-task plan with TDD discipline at every step. Each task has explicit
file paths, complete code blocks, expected test output, and a commit
template. Coverage maps 1:1 to the spec: core data model (Tasks 2-7),
CLI subcommands (Tasks 8-12), cold-start latency guard (Task 13),
async watcher (Task 14), Textual TUI (Tasks 15-17), curriculum
migration (Task 18-19), and CI/docs (Task 20).
Adds pyproject.toml with hatchling backend, package directories,
__main__ entry point, and a stub CLI that prints the version. Removes
pylings.sh, pylings.py, and the old per-exercise test files (those will
be folded into the exercises themselves in a later task).
These were committed before .gitignore covered them. .gitignore already
excludes __pycache__/ in the global Python block, so they won't return.
Exercise is a frozen dataclass describing one curriculum item plus the
done-marker check. RunResult is the value returned by the subprocess
runner (added now alongside Exercise; the runner itself comes next).
Four fixture exercises covering the failure modes the runner has to
distinguish: passing, AssertionError, SyntaxError, and 'tests pass but
the I AM NOT DONE marker is still present'. Used by manifest, runner,
and CLI integration tests.
Loads and validates the curriculum manifest: format_version must be 1,
exercises array must be non-empty, paths must exist, names must be
unique. Welcome and final messages default if omitted.
run() spawns python <exercise.py>, captures stdout/stderr with forced
UTF-8 encoding, enforces a 5s timeout, and returns a RunResult. The
'passed' flag combines exit code 0 with the absence of the # I AM NOT
DONE marker. run_verify() is a sibling that ignores the marker — used
by 'pylings verify' for CI and curriculum-author validation.
State tracks completed exercises and the current pointer. Atomic writes
via tmp-and-rename. Corruption recovery: bad JSON gets backed up to
.bak and a fresh state is returned. Sets serialized as sorted arrays
for deterministic diffs.
snapshot() copies a pristine exercise to .pylings/originals/ once;
subsequent calls are no-ops. restore() overwrites the live file from
the snapshot. Missing snapshot raises ResetError with a clear message.
argparse dispatch with --root and --version globals plus subcommand
stubs. verify is wired end-to-end: loads the manifest, runs each
exercise with run_verify (marker-agnostic), exits 1 on first failure
or 2 on manifest errors. Textual import deferred behind the watch
branch so subcommands stay fast.
Prints every exercise in curriculum order with a status marker:
✓ (completed), ● (current), 🔒 (locked / not yet reached). 'Current'
is the saved current or, if none saved, the first pending exercise.
Looks up the exercise by name in info.toml and prints its hint. Unknown
names exit non-zero with a clear error.
Runs a single exercise and prints its output. Distinct exit codes for
script failure, timeout, and the 'tests pass but marker still present'
case. Stderr/stdout are passed through so tracebacks remain real.
Snapshots every exercise on the first command that touches files
(verify, list, run, reset, watch). reset restores from .pylings/
originals/ after a y/N prompt, or unconditionally with --yes.
Asserts that 'textual' is never imported during pylings hint / list /
run / verify. Uses python -X importtime and greps the trace. Prevents
accidental top-level Textual imports from regressing subcommand
responsiveness.
watch() is an async iterator yielding once per debounced change to the
target file. Caller decides what to do with each event (typically
re-run the exercise).
ProgressBar renders a fixed-width unicode bar plus the X/Y count.
Stylesheet sets up the three-region layout (top progress, middle main
split into tree+output, slide-down hint section).
abhiksark added 22 commits May 20, 2026 17:47
Ten exercises covering writing, reading, the with statement, line
handling, append mode, iteration, writing line lists, transforming
contents, word counting, and a copy-with-transform challenge, on
an easy-to-hard ramp. Each exercise works on a tempfile and cleans
up. Verified: every broken form fails and a correct solution passes.
Twelve exercises covering class definition, __init__, instance
attributes, methods, self, default attributes, method composition,
__str__/__repr__, class vs instance attributes, factory methods,
and a complete-class challenge, on an easy-to-hard ramp. Verified:
every broken form fails its check and a correct solution passes.
Ten exercises covering lambdas, map, filter, sorted with a key,
higher-order functions, returning functions, functools.reduce,
any/all, and a data-pipeline challenge, on an easy-to-hard ramp.
Verified: every broken form fails its check and a correct solution
passes.
Ten exercises covering higher-order functions, simple decorators, @
syntax, preserving return values, *args/**kwargs wrappers,
functools.wraps, call counting, decorator factories, stacking, and
a memoize challenge, on an easy-to-hard ramp. Verified: every
broken form fails its check and a correct solution passes.
Ten exercises covering yield, generator consumption, for-iteration,
generator expressions, next(), guarded infinite generators, the
iterator protocol (__iter__/__next__), and a streaming-transform
challenge, on an easy-to-hard ramp. Verified: every broken form
fails its check and a correct solution passes.
Eight exercises covering the with statement, automatic cleanup,
__enter__/__exit__, returning a value from __enter__,
contextlib.contextmanager, exception suppression, and a
managed-resource challenge, on an easy-to-hard ramp. Verified:
every broken form fails its check and a correct solution passes.
Eight exercises covering @DataClass basics, typed fields,
defaults, default_factory, the generated __init__/__repr__/__eq__,
frozen dataclasses, and a dataclass-with-a-method challenge, on an
easy-to-hard ramp. Verified: every broken form fails its check and
a correct solution passes.
Eight exercises covering variable and function annotations,
parameterized generics, Optional, tuple types, type aliases,
Callable, and a fully-annotated-function challenge, on an
easy-to-hard ramp. Verified: every broken form fails its check
and a correct solution passes.
Ten exercises covering re.match, re.search, re.findall, character
classes, quantifiers, anchors, capturing and named groups, re.sub,
and a structured-data-extraction challenge, on an easy-to-hard
ramp. Verified: every broken form fails its check and a correct
solution passes.
Twelve exercises covering assertions, test functions, multi-case
and parametrized tests, verifying raised exceptions, fixture-style
setup, edge cases, testing a class, and a small-test-suite
challenge, on an easy-to-hard ramp. Verified: every broken form
fails its check and a correct solution passes.
Five testing checks inspected the test source for substrings like
'assert' and 'ValueError' — but those words also appear in the
exercise's own comments, so an unsolved exercise (marker removed)
passed falsely. The guards now strip comment lines before searching,
and testing8 asserts its case list is genuinely populated.
Eight exercises covering base cases, factorial, recursive sums,
counting, Fibonacci, string reversal, recursion over nested
structures, and a recursive-search challenge, on an easy-to-hard
ramp. Verified: every broken form fails its check and a correct
solution passes.
Eight exercises covering import, from-import, import-as, the random
and string modules, the __name__ variable, the __main__ guard, and
a combined-imports challenge, on an easy-to-hard ramp. Verified:
every broken form fails its check and a correct solution passes.
Ten exercises covering Counter, most_common, defaultdict for
counting and grouping, namedtuple, deque as a stack and a queue,
Counter arithmetic, and a tally-and-rank challenge, on an
easy-to-hard ramp. Verified: every broken form fails its check and
a correct solution passes.
@abhiksark abhiksark changed the title Rebuild pylings as a Rustlings-style interactive learner Improve pylings interaction model May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant