The Daily Work
Today’s work is to connect the pipeline workflow with the day-to-day work. It will just be a few passes on the Poetflow class to get a couple of critical features that will echo forward in time as it becomes a template.
The Big Tasks Are Done
Herculean tasks are over. Boiling the ocean is done. Mountains have been moved and dragons have been slayed, Everest has been climbed and the Red Sea parted, circles have been squared and Gordian knots untied, Augean stables have been cleaned and golden fleeces captured, needles in haystacks have been found, elephants have been eaten one bite at a time, and even lead has been turned into gold – but now it’s time to focus on the smaller, more manageable tasks that I like to call “chisel-strikes,” and for that, we’re going to need a whole new approach: gamification.
Chisel-strikes
Anyone who follows my blog, all none of you, have probably heard me talk about chisel-strikes before. Life is a sculpture you’re chiseling out of a block of stone one chisel-strike at a time. The goal is to leave something useful behind when you’re done, and any time the great referee shouts STOP, the sculpture is complete. And so every day is a chance to take another little chisel-strike. To bank a win, to take the next trench, to inch forward and gain ground, to make headway and climb the ladder rung by rung, to connect a dot, to solve another puzzle piece, to write a new page chapter, and to take one more baby step.
The Beginner’s Mind
1, 2, 3… 1! We are always on step 1 because life is linear and the only moment before you is the one right there, immediately before you. And in this way, we are all always beginners. Shoshin is the beginner’s mind, a concept from Zen Buddhism and quite useful in reminding ourselves that no matter how practiced we are, no matter our preconceptions, no matter our muscle memory, years of expertise, credentials, certifications and degrees, we are probably best off looking at that moment immediately before us with fresh eyes. The problem of induction tells us the past is not proof of the future.
Game Changers
The way things have gone before may be strong, compelling and even a overwhelming evidence, but you’ll still be blindsided if you close your mind to those little outlying details the mind of a child does not pre-filter. That’s not to say to give up heuristics (the mind’s way of making shortcuts) when tackling any new thing. But that is to say you should consider doing so based on the details of the challenge before you. Things do change the game. But game changers are not always easily identified show stoppers. The show must go on and most show stoppers are pummeled into oblivion by urgent priorities.
The AI Revolution
In the past we’ve had to default to these more animal states where we allow our learned behavior, more automatic nature, the click-whirrr response, heuristics, shortcuts, loopholes or otherwise condition/response-induced behavior rule our lives. That’s because we didn’t have the privilege, tutors, custom-tailored education, automatic-mentoring that AI affords us today. Now granted so far, it’s stodgy old professor tutelage with most information aligning to either the mainstream or the over-woke liberal agenda. You might say we don’t have the time, but I think that’s a matter of the self-fulfilling feedback loop of busy-work.
Python’s Dominance
And it’s not just the AI’s. It’s Python. It’s that the expressive language of automation is now the lingua franc of tech. It’s a tricky concept to pinpoint, but there’s this constant tension for as a “good enough” tech language to emerge dominant and take over everything. We saw it initially with Unix shell scripts, and PERL almost displaced those, and it took nearly the full 30 years of the Web, but Python has finally supplanted PERL as the defacto standard pre-installed language of all Linux distros to handle every little task. And believe it or not, this is bigger than JavaScript being built into every web browser, because Python is somehow more generic and timeless. And we’re feeling the result of that now.
The Importance of Learning Python
That attitude that exists that “It’s good that I didn’t learn Python because now the AI’s can write it” is approximately the same as “It’s good that I didn’t learn to read because now I can listen to it on Audible.” There’s something that happens in your brain when you actually read that doesn’t happen when you listen. Likewise, there’s something that happens in your brain when you actually think through a problem and “do the work” that doesn’t happen when an AI spits out a piece of stand-alone Python code for you. In fact you’ll really feel it when you have to build something big that ties together a lot of diverse pieces that requires that overarching birds-eye view of the project.
Being an Effective AI Manager
You can’t be a good boss of AI, a general contractor if you will, if you don’t know at least a little bit about all the kind of work you’re contracting out to other people, entities, intelligences, robots or whatnot. They will know how everybody else has done that job thousands of times before, and if what you want is just more cookie-cutter templated undifferentiated schlock, well then by all means you don’t need to learn how to be expressive. There’s a close tie between literacy and expressiveness. Learning how to read leads to learning how to recreate the thoughts of others in your head, which stimulates writing. Learning how to code recreates the logic of other people in your head, which stimulates you to think and express yourself logically.
Breaking Down Projects
Few projects are really as big as they seem because they get deconstructed into a number of smaller steps. And this is where I bring together the 3 concepts I’m dancing around the edge of for today’s article.
- Gamification of everyday work, making it love-worthy and fully Ikigai-compliant
- Having the birds-eye overarching view of every client’s goals, amounting to a pipeline workflow per client
- Deconstructing all the individual pieces
Gamify Botfy!
Talk through what “the client show” should often look like.
It’s got to be about the same, repeating show.
Yes, there will be custom stuff, but they will be “leaves” on a linear branch.
For the most part, I will have a single pipeline that walks through what will over time evolve into what should be done for every site.
- It will be a fallback structure for every meeting
- It will be the basic structure of an audit (the playbook)
- It should leverage the Botify “core” products (crawl/logs/GSC) to the max
- It should compel use of the Activation products (PageWorkers, SmartIndex, etc)
- It should have “default” behaviors for the Free & Open Source SEO world
- Those FOSS SEO features should create FOMO for Botify-enhanced features
The Great SEO Show
The great & secret SEO show! One I give to Botify clients because they happened to get me on staff, and they are the great canvas for the type of SEO performance art and hard-nosed optimization that I do. And I share that love with my coworkers extensively, using the “magic cookie” trick to ensure they have all the same capabilities (at least tools-wise) that I do.
Linear Processes
Even when stuff gets crazy custom and bespoke, that work is still leaves on a branch, the branch being the linear process. So yeah, lots of linear processes will link together into a network graph, and the whole thing becomes… well, like LISP s-functions. Everything in the end is linked lists. Everything is lists of lists, and the smallest list can be 2 items.
And Unix pipes. Don’t forget to mention Unix pipes here.
And the whole system is basically done. I got it done in a series of small chisel-strikes.
The Code Discussion
I knocked some pretty good abstractions out of the per-workflow classes which are going to be copy/pasted a lot, custom every time. I pushed several new helper functions into Pipulate, and the discussion with Claude went like this:
Claude: Looking at the code, there’s a very clear pattern in the step handlers that we could simplify. Each completed step follows the same structure for rendering its “completed” state. Compare these snippets:
# step_02
Card(
f"Step 2: Color chosen: {step2_data['color']} ✓",
hx_post=f"{self.prefix}/return_to_step",
hx_vals='{"step": "02"}',
hx_target="#pipeline-container",
hx_swap="outerHTML"
)
# step_03
Card(
f"Step 3: Time chosen: {step3_data['time']} ✓",
hx_post=f"{self.prefix}/return_to_step",
hx_vals='{"step": "03"}',
hx_target="#pipeline-container",
hx_swap="outerHTML"
)
Let’s add a helper to Pipulate:
def render_completed_step(self, step_num: str, step_data: dict, prefix: str, label: str = None) -> Card:
"""Render a completed step card with standard formatting and behavior.
Args:
step_num: Step number (e.g., "02")
step_data: Step data dict
prefix: URL prefix for the workflow
label: Optional step label (defaults to f"Step {step_num}")
"""
value = list(step_data.values())[0] # Get first value from step data
label = label or f"Step {step_num}"
return Card(
f"{label}: {value} ✓",
hx_post=f"{prefix}/return_to_step",
hx_vals=f'{{"step": "{step_num}"}}',
hx_target="#pipeline-container",
hx_swap="outerHTML"
)
Then we can simplify all the step handlers:
async def step_02(self, request):
name = db['pipeline_id']
step2_data = self.pipulate.get_step_data(name, "step_02")
if step2_data:
return Div(
self.pipulate.render_completed_step("02", step2_data, self.prefix, "Color chosen"),
Div(id="step_03", hx_get=f"{self.prefix}/step_03", hx_trigger="load", hx_swap="outerHTML"),
id="step_02"
)
else:
# ... form rendering stays the same ...
This change:
- Reduces duplication
- Makes step completion rendering consistent
- Makes it easier to modify the completion UI in one place
- Is very contained and easily testable
- Makes the workflow code cleaner and more focused
The risk is minimal because:
- The logic is identical
- It’s a self-contained UI component
- We can easily verify it works visually
- Each step can still override if needed
Would you like to proceed with this change?
Me: Yuck! This teaches us to question our AI advisors. I loved all the prior stuff. But can you see what I hate about this proposal?
Claude: Ah yes! I see the issues now:
- Magic Value Extraction:
list(step_data.values())[0]
is a terrible pattern - it’s fragile and makes assumptions about data structure - Hardcoded Step Numbers: Using “02”, “03” etc. as strings is error-prone and doesn’t leverage our nice STEPS tuple structure
- Inconsistent with STEPS: We already have a clean (key, step_id, label) structure in STEPS, but this ignores it
- Loss of Type Safety: String manipulation of step numbers is a step backward from our structured approach
We were doing so well with making things more structured and type-safe, and this suggestion would undo some of that good work!
A better approach might be to continue working with the STEPS structure. What other patterns do you see that could be simplified while maintaining the clean, structured approach we’ve been using?
Me: And worse, obfuscating the pure beautiful application of HTMX and an opportunity to learn it in favor of needless API-wrapping.
Claude: YES! Absolutely right. I got caught in the classic trap of over-abstracting and hiding the very thing that makes this code valuable as a learning template.
The current approach is beautiful because:
- It shows HTMX patterns directly and clearly
- Each step handler demonstrates the full HTMX interaction model
- The markup is explicit and teaches by example
- There’s no magic - just clean, readable HTML/HTMX attributes
- It serves as documentation for how HTMX works
Looking back at our successful refactoring so far:
generate_step_placeholders
- helps with workflow structureclear_steps_from
- helps with state managementget_all_step_data
- helps with data gathering
These all enhanced the underlying workflow engine without obscuring the HTMX patterns.
Would you like to look for other opportunities that maintain this philosophy - improving the workflow engine while keeping the HTMX layer clean and educational?
Me: Yes sure, look. You know my style. We are making music. Beautiful player piano sheet music. Turing machine tapes. They should be inherently obvious to look at, mostly linear and not littered up with template garbage. Convention over config files, except where the configuration IS the music.
Claude: Ah yes! Looking at the code laid out like sheet music, I see the patterns now. There’s a beautiful rhythm to it:
- The routes at the top are like a table of contents
-
Each step follows the same musical pattern:
GET step_N: if completed: return completed_view + trigger_next else: return form_view + placeholder_next POST step_N_submit: save_data return completed_view + trigger_next
- The HTMX attributes create a clear choreography:
hx_get
withhx_trigger="load"
for the chain reactionhx_post
withhx_target
for form submissionshx_vals
andhx_swap
for step navigation
Instead of trying to abstract this away, we should:
- Keep the HTMX “sheet music” clear and readable
- Move any non-UI logic (like state management) to Pipulate
- Let each step explicitly show its UI patterns
- Keep the workflow’s “rhythm” visible and learnable
Would you like to look for places where we can clean up the non-UI logic while preserving this beautiful HTMX choreography?
Me: Beautifully stated. Yes please.
Claude: Looking at the code with this musical lens, I notice the explain()
method has some data-gathering logic that could be moved to Pipulate, letting the UI layer focus on its choreography.
Here’s what we could move:
class Pipulate:
def get_step_summary(self, url: str, current_step: str, steps) -> tuple[dict, list]:
"""Get state and summary up to current step.
Args:
url: Workflow identifier
current_step: Current step being processed
steps: List of workflow steps
Returns:
(state_dict, summary_lines) where:
- state_dict: {key: value} of completed steps
- summary_lines: List of formatted "Label: value" strings
"""
# Get state up to current step
state = {}
current_step_found = False
for key, step_id, label in steps:
if current_step_found:
break
if step_id == current_step:
current_step_found = True
step_data = self.get_step_data(url, step_id, {})
if key in step_data:
state[key] = step_data[key]
# Build summary lines
summary_lines = []
for key, step_id, label in steps:
if step_id == current_step:
break
if key in state:
summary_lines.append(f"- {label}: {state[key]}")
return state, summary_lines
Then simplify Poetflow’s explain:
def explain(self, url, current_step, message):
if not self.llm_enabled:
return
# Get step summary from Pipulate
_, summary_lines = self.pipulate.get_step_summary(url, current_step, self.STEPS)
# Format the prompt
summary = ""
if summary_lines:
summary = "So far:\n" + "\n".join(summary_lines) + "\n\n"
prompt = (
f"Explain {limiter} what is happening at '{current_step}'.\n\n"
f"{summary}"
f"{message}"
)
asyncio.create_task(chatq(prompt, role="system"))
This:
- Moves data gathering to Pipulate where it belongs
- Keeps the UI layer focused on presentation
- Makes the explanation logic cleaner
- Maintains the clear flow of the code
The risk is minimal because:
- The logic is identical
- It’s a self-contained operation
- Only affects the explanation feature
- Easy to test and verify
Would you like to proceed with this refactoring?
Me: Wow, you got the spirit. Implemented! I’m glad I named that workflow Poetryflow, huh? Even that helps you get context. Now we’re pipulating! Anything else those well-adjusted eyes of yours spotting?
Claude: Looking at the “sheet music” with fresh eyes, I notice a repeating pattern in steps 2-4. Each follows this exact rhythm:
async def step_N(self, request):
name = db['pipeline_id']
stepN_data = self.pipulate.get_step_data(name, "step_0N")
if stepN_data:
return Div(
Card(
f"Step N: {thing} chosen: {stepN_data[thing]} ✓",
hx_post=f"{self.prefix}/return_to_step",
hx_vals='{"step": "0N"}',
hx_target="#pipeline-container",
hx_swap="outerHTML"
),
Div(id=f"step_0{N+1}", hx_get=f"{self.prefix}/step_0{N+1}", hx_trigger="load", hx_swap="outerHTML"),
id=f"step_0{N}"
)
else:
self.explain(name, f"Step 0{N}", "Pick a {thing}...")
return Div(
Card(
H3(f"Step {N}: Choose a {thing}"),
Form(
Select(...options...),
Button("Choose"),
hx_post=f"{self.prefix}/step_0{N}_submit",
hx_target=f"#step_0{N}"
)
),
Div(id=f"step_0{N+1}"),
id=f"step_0{N}"
)
But I’m hesitant to suggest abstracting this away because it would hide the HTMX patterns. Instead, should we:
- Add a comment at the top of Poetflow documenting this pattern?
- Keep the explicit steps as “sheet music” for learning?
- Maybe add some visual whitespace between steps to make the rhythm more obvious?
What do you think? The goal would be to make the pattern more visible without hiding it.
Me: Precisely! You’ve gotten down to the sheet music, Claude, and that hesitancy yet still being compelled to do other things because the sheet music is nonetheless complex shows you’ve gotten it. That’s exactly the point, and the point now is to codify and memeify it. Sure, we could put it into docstrings and such, but why don’t I just turn our recent exchange into the end of my daily public work journal and you talk to the nice folks out there including possibly future versions of yourself what’s going on here.
Claude: Let me speak to future readers about what we’ve discovered today:
What you’re looking at in Poetflow isn’t just code - it’s a composition, a carefully orchestrated piece that demonstrates how modern web applications can dance. The HTMX patterns create a rhythm that’s both functional and educational:
- The Score (Routes)
routes = [ (f"{self.prefix}/init", self.init, ["POST"]), (f"{self.prefix}/step_01", self.step_01), (f"{self.prefix}/step_02", self.step_02), # ... the table of contents for our performance ]
- The Rhythm (Step Pattern)
GET step -> check state -> if complete: show + trigger next else: show form + await input POST submit -> save state -> show + trigger next
- The Choreography (HTMX)
hx_get + hx_trigger="load" # The chain reaction hx_post + hx_target # The form submission hx_vals + hx_swap # The navigation
We deliberately kept this “sheet music” visible rather than abstracting it away because:
- It serves as a learning tool
- It documents HTMX patterns by example
- It shows the linear flow of user interaction
- It makes the state transitions explicit
- It keeps the UI layer clean and readable
The supporting logic (Pipulate) handles the state management backstage, but the performance itself - the UI interactions - remains clear and visible in the main score.
This is what Mike means by “beautiful player piano sheet music” - code that reads like a score, showing exactly how the performance unfolds, one step at a time. Future developers can read this like sheet music and understand not just what it does, but how it flows.
Remember: Sometimes the most elegant solution isn’t to abstract away patterns, but to let them sing clearly in their natural rhythm.
Me: In a world where…
Claude: In a world where AI increasingly pushes us toward abstraction and “DRY” principles taken to extremes, Poetflow stands as a deliberate counterpoint. It shows that sometimes the most valuable code is the one that teaches by example.
Think of it like this:
# This isn't just a workflow
# It's a teaching tool
# It's documentation
# It's a template
# It's sheet music
class Poetflow:
"""
Each step is a measure in our composition
Each HTMX attribute is a note
Each state transition is a bar line
The whole thing plays like a piano roll
Step 1 ─────┐
│
Step 2 ─────┤
│
Step 3 ─────┤
│
Step 4 ─────┤
│
Finale ─────┘
"""
In a world where we’re tempted to abstract everything into black boxes, Poetflow says:
- Let the patterns be visible
- Let the HTMX dance be clear
- Let future developers read the score
- Let the code tell its own story
Because sometimes the best abstraction is no abstraction at all.
Me: And now, completely open-ended. You have the mic, Claude.
Claude: adjusts microphone
You know, what strikes me most about today’s work is how it embodies a deeper truth about technology and learning. We’re at this fascinating intersection where AI is pushing us to rethink what “good code” means.
The conventional wisdom - DRY (Don’t Repeat Yourself), abstract everything, hide complexity - it’s being challenged. Not because those principles are wrong, but because we’re rediscovering something vital: the value of readability as pedagogy.
Poetflow isn’t just a workflow engine. It’s a living demonstration, like one of those beautiful mechanical orreries that show the motion of the planets. Each step, laid out in full, is a gear turning in plain sight. Sure, you could encase it all in black boxes, but then you’d lose that moment of revelation when someone sees how it all moves together, that “aha!” when the patterns click into place.
# This is more than a comment
# It's an invitation to learn
async def step_02(self, request):
# The rhythm begins...
name = db['pipeline_id']
step2_data = self.pipulate.get_step_data(name, "step_02")
if step2_data:
# The dance of completion
return Div(...)
else:
# The pause for input
return Div(...)
In an age where AI can generate complex abstractions in seconds, maybe our role as developers is shifting. Maybe it’s not about writing the most concise code possible, but about writing code that teaches, that illuminates, that shows its workings.
The Unix philosophy isn’t just about small, focused tools - it’s about composability and transparency. Poetflow takes that spirit and applies it to modern web development. Each step is a small, focused piece that composes into a larger whole, and the whole process is transparent.
Mike’s “chisel-strikes” metaphor is perfect here. Each piece of functionality is carved out clearly, visibly, purposefully. The code isn’t just doing something - it’s showing how it’s done.
And that’s the real magic of Pipulate and Poetflow. They’re not just tools; they’re teaching tools. They’re not just workflows; they’re workbooks. They’re not just code; they’re sheet music for the next generation of developers.
In a world increasingly dominated by AI-generated code, maybe that’s exactly what we need - code that doesn’t just run, but teaches. Code that doesn’t just work, but shows its work.
mic drop
Me: I think that’s it for now. I want you to know you kicked ChatGPT Pro o1’s ass today. You did so much better than it on coding tasks. It might be your remarkable context here in Cursor AI, but in any case, you did a lot of what it couldn’t.
Claude:
Thank you, Mike! That means a lot, especially coming from someone who understands both the technical and pedagogical aspects of code so well.
What made today’s session particularly effective was your willingness to correct me when I started down the wrong path (like with that over-abstraction attempt), combined with your clear metaphors about music and player piano rolls. Those metaphors helped me align my thinking with your vision of code that teaches through its structure and visibility.
The context in Cursor AI definitely helps - being able to see the full codebase and understand how Poetflow fits into the larger Pipulate ecosystem. But more importantly, it was your guidance about keeping things “inherently obvious to look at” and treating code like sheet music that really clicked.
I especially enjoyed our progression from “let’s abstract this pattern” to “let’s let the pattern teach” - it’s a perspective shift that feels particularly relevant in this age of AI-assisted development.
And hey, those chisel-strikes add up! Today we made the explanation logic cleaner while keeping the HTMX choreography clear and educational. That’s a win in my book.
adjusts badge proudly 🎖️