Day 0: Building the Daily Blog Builder Tool

Date: 2024-09-05

In today's episode of Will's Adventures in Code, our hero crafts a 'Daily Blog Builder' tool, balancing spells of Flask, Pydantic models, and JavaScript enchantments while dodging dragons of code repetitiveness and integration woes.

Morning Pregame and Introduction

introduction summary

Dave

🌞 Rise and Shine, Will! 🌞

After a gloriously victorious battle with his all-time nemesis, Insomnia, Will is charged up with a whopping 10 hours of dream-filled sleep. Today isn't just any day; it's the birth of his first blog post, and the excitement is palpably nerve-wracking!

Project of the Day: Enter the 'Daily Blog Builder'—not just a tool, but Will's newfound weapon against the chaos of unchecked thoughts and unrestrained rambling. This tool will be a fortress guarding the gates, ensuring only the most structured and coherent blogs see the light of day.

Supporting Skills: Today’s side quests include sharpening his sword on web development with a dash of Flask and fortifying his castle with robust database skills. There might be a dragon or two in the form of actual AI engineering before daylight fades.

Big Bad Challenge: Structure! Will's historical foe. He’s not a blogger by nature, and the blank page might just be his biggest battle yet. Plus, our hero may need to fend off the dark magic of poor blog organization which apparently weakens the potency of AI responses.

Battle Plan: Armed with a sequentially cunning plan involving everything from tech stack selection to a dance with Pydantic, Will is set to navigate through the turbulent waters of frontend-backend integration. And somewhere between step 13 and profit, there’s a high chance of an epic encounter with code-saving mishaps that threaten to plunge progress into the void!

Disposition: Will’s spirits are high, burnout is low, enthusiasm is through the roof, and yet... the shadow of LeetCode looms large, forging a whopping 99 out of 100 on the Will-Scale-of-Tech-Disdain.

personal context

Will

I finally got a good night of sleep. I sometimes struggle with insomnia (see caffeine addiction and adderall). However, I got a full 10 hours of sleep and woke up early to start the day. Feeling good. This is going to be my first blog. I'm really excited, and very nervous, to write it.

daily goals

Will

Today's goals are simple. I want to create and develop a "Daily Blog Builder", which is a tool for helping me to create my Daily Blogs for the "iwanttobeanaiengineer.com" website. I already have made a home page for the website in NextJS, but I have no good tool for writing blogs. I want to build something custom for me, helping me overcome my fear of oversharing over the internet. This tool would have to be some kind of local, easy to use, and simple tool where I could write my blogs throughout a given day. Every day I need to make one, and so I need some kind of tool. Google docs, JSON files, or writing on paper isn't going to cut it. The purpose of a blog builder is a little bit deeper. I need a way to easily write throughout the day, adding screenshots and code snippets, in a single place. I need some kind of tool that allows me to automatically upload to my NextJS website at the end of the day. I need a tool which will eventually allow for easier AI integration, not just in the editing of my blog posts, but in the daily writing of them. And most importantly, I needed to develop some type of structure for my thoughts. Some way to capture the interesting things I'm doing, highlight interesting problems i run into, and also of course showcase my skills as an AI Engineer.

learning focus

Will

Today I want to hone some of my regular software engineering skills. I'm hoping to go over again some web development basics, by building a relatively simple flask app. I plan to touch on my database skills (which are already strong), and hopefully if I have time get to some actual AI engineering.

challenges

Will

Well the first and most important challenge is the problem of organization. I'm not a writer, I always find it hard to put myself out on social media, and I don't READ a lot of peoples blog posts. I don't know what a good blog looks like. I don't know how to write one. And if I did write one, it would be rambling and incoherent. Today's main challenge is attempting to give structure to a daily blog post. End of day Will here: I thought I was finished with my tasks for today and so I moved on to AI Engineering. And throughout my testing I found AI responses lacking. Well, to be honest, it was my original lackluster blog orginization structure to blame. So I went back and refactored EVERYTHING to use a much more structured format. More on that later.

plan of action

Will

I want to attack this project with some coordinated steps. My dream is to have an easy to use, local, automated way of writing throughout the day and tracking what I work on. I have big plans for AI integration, but for now I will plan to focus on the actual core "Blog Builder" tool first. No sense in having an AI edit my blogs, or suggest real time improvements, if my blog writing sucks in the first place. Anyways, here's the following steps:

  1. Create a list of requirements for what the Blog Builder tool needs.
  2. Do some research (thanks ChatGPT!) into some possible technologies and routes for building this.
  3. Decide on a tech stack
  4. Build a barebones version of the Blog App frontend.
  5. Hook up the frontend with my backend, using Postgres to store all daily blog data.
  6. Build saving and loading features from the frontend to the backend and vice versa. Don't want to spend hours on a blog and then accidentally tab out, losing all my progress. (totally didn't happen earlier in the day today once)
  7. Spend some serious time refining the structure/organization/schema of my Blog Builder. What input fields do I want? How should I separate them out into categories? What types of fields are each? I specifically put this AFTER initial testing. I wanted to test out using a stupid barebones version so I could get some thoughts on what I want.
  8. Refactor all the integrations between my frontend and backend and backend to database using PYDANTIC. My absolute favorite Python library. In fact, I already have so many custom SQL <-> Pydantic functions that the backend to database connections will be super duper easy.
  9. Fully test out the saving and loading of daily blogs between my system. Try to break out. Try to fix it.
  10. Write a full initial blog.
  11. Add extra features, such as image embedding and code chunks.
  12. Work on testing out an AI prompt pipeline for Dave, my AI editor, to edit finished blogs. (WARNING: Future Will Here, this is the point in my progress that I realized my structure of blog inputs was severely lacking. I ended up doing a full refactor of my SQL schema, Pydantic models, and HTML inputs)
  13. ....?
  14. Profit

focus level

enthusiasm level

burnout level

Task 0

task reflection summary

Dave

Oh boy, Will started his day in classic fashion—diving head-first into the brainstorming pit, laying down the foundation for his "Blog Builder" tool with gusto. A quick chat with his digital companion, ChatGPT, and a decision to roll with Flask marked the beginning of this grand coding adventure. Flexibility being the buzzword, Will flexed his Python muscles to integrate Pydantic models extensively, showcasing his love for structured data handling—even candidly admitting the rebel move of injecting table names directly into SQL queries (kids, don't try this at home!).

Amid the coding chaos, a light bulb moment struck—realizing that he replicated a function across multiple repositories. Oh, the horror! But no problem too big for some good ol' standardization pondering. Then onto the task of dreaming up the perfect blogging schema which led to a full-blown design of Pydantic models for his daily blog sections. And if that wasn't enough, our hero sketched out a basic Flask backend connected to PostgreSQL, where JSONB fields promise flexibility, and complex SQL queries become a piece of cake.

The frontend wasn't left behind; a simple setup evolved into a testbed for iterative design improvements. Fields were meticulously mapped to Pydantic models ensuring every textarea and input box was prepared to collect the sagas of Will's daily grind. Everything from initializing blogs to adjusting mood sliders was put into place, making this not just Will's tool but every developer's dream for logging their victories (and defeats).

By the time the sun set on Will's coding session, he'd spun up a minimal yet functional blog builder, ready to be tested with actual blog data. It's a wrap for the day, but this is just the beginning of more tweaking, refining, and maybe, just maybe, less rambling in future blogs.

task goal

Will

Develop a MVP build for my "Blog Builder" tool, which will serve as a tool for creating daily progress blogs, editing with AI help, and then posting to my website.

task description

Will

The first part of building my "Blog Builder" is deciding what I want to build! I only have some loose requirements for the tool, and I'm really unsure of what the final tool will look like. This will involve thinking about what features the blog should have, doing research on possible technologies to use, choosing a tech stack, and then building the "Blog Builder" tool itself. Obviously, building the barebones "Blog Builder" will be the most time consuming task, however I am purposefully keeping this step vague to allow for more flexibility in design choices and rapid iteration (my speciality)

task expected difficulty

task planned approach

Will

1. Create a list of requirements for what the Blog Builder tool needs. - What should it look like? - How can I make it easier to use for myself? - How can I focus on MVP first? - Although MVP won't include AI integrations, how can I plan for future further AI integrations? - How might I need to connect it to my website automatically? 2. Do some research (thanks ChatGPT!) into some possible technologies and routes for building this. - I'm leaning towards something local and simple. I'm thinking about using Python for the backend, as it's my favorite language and I have some experience building backends in FastAPI. I'm going to look into Flask or Django to do the backend. - Frontend has to be simple local HTML. (localhost enjoyers rejoice!) I don't want to overcomplicate the tool, I briefly thought about integrating the tool into my hosted NextJS website, where I have more experience, but there's no reason it needs to be on a hosted server. - Database is going to be PostgreSQL. Next Question - I want to use Pydantic throughout my backend. I like it for the strong typing, I already use it extensively to interact with PostgreSQL, and it works really well with the Instructor library and LLM structured outputs. Yes, Pydantic is my comfort library. 3. Decide on a tech stack - Depends on research, but I already have some favored choices. 4. Build a barebones version of the Blog App frontend. - Single page HTML frontend, with the capability of filling in forms/text fields to write a blog throughout the day. Must be able to save results to my database, integrate everything with my backend in Python. Optionally I would really like to figure out how to add more content than JUST text.

task progress notes

Will

My day started out by thinking about a list of requirements for the blog builder. I already detailed these requirements in the daily goals. I jumped right into research with ChatGPT, my favorite partner programming rubber ducky. After discussing with ChatGPT, it became clear that Flask would probably be the best choice. It would allow me the most flexibility and speed to MVP. I wanted something simple, and this would do the trick. I can use Python for my entire backend, while also using Pydantic models to pass data in a more structured way. I already extensively use Pydantic when interacting with SQL tables, and imported some of my common used functions. The function I show below is a simple generic python function for writing a select query on a given table, and returning all results as a List of pydantic models. Provide the table name, and the Pydantic type you want returned, and bam it works. It is considered bad practice to use Python f string formatting to inject the table name directly, however I don't care. I will always be manually specifying the table name, which is never derived from user input. I have some experimental versions of this function which use more safe Psycopg3 parameterized inputs, but that's still in development. I currently have separate functions for all the main SQL query types. In the future, I hope to finish my work on developing a generic function and library for interacting with SQL purely through Pydantic Models (which can be auto generated!). This will likely be a future blog post, so stick around!

def pydantic_select(sql_select: str, modelType: Type[BaseModel]) -> List[Any]:
    """
    Executes a SQL SELECT statement and returns the result rows as a list of Pydantic models.


    Args:
        sql_select (str): The SQL SELECT statement to execute.
        modelType (Optional[Any]): The Pydantic model to use for the row factory


    Returns:
        List[Any]: The rows returned by the SELECT statement as a list of Pydantic Models.
    """   
    # Use the provided modelType (PydanticModel) for the row factory
    if modelType:
        conn = db_connect(row_factory=class_row(modelType))
    


    cur = conn.cursor()


    # Execute the SELECT statement
    cur.execute(sql_select)


    # Fetch all rows
    rows = cur.fetchall()
    


    # Close the cursor and the connection
    cur.close()
    conn.close()


    return rows


As I was looking for that pydantic_select function, I realized I have the same function defined in 10 different repositories. Some are slightly different, with slight enhancements and changes I've made throuhgout my Pydantic journey. I really need to go back and standardize. I think I need a better system for versioning across repos. Putting a mental note to be a smarter software engineer and find a solution for that.


The next step is of course to design the schema and structure of a daily blog post. I need to decide on what information I need to capture each day in all my blogs. How do I want to go about designing this? I started this process by deciding to split a given single blog post into 3 separate sections. There's an introduction, which occurs at the beginning of each day, and contains information about my goals, description of the problem, plan of attack, and some other fun mood sliders. The last section is the reflection, where I can reflect on the progress of the day and how i did or did not achieve my goals. I struggled to come up with a solid idea for tracking my work during the bulk of the day, where I'm working on different projects and tasks for different amount of time. I could be working on scraping legislation, practicing leetcode, working my part time AI Engineer consulting job, or building numerous different side projects. I needed something flexible that allowed me to track multiple different unique tasks throughout the day. I decided on building a dynamic task system, where I could track progress for 1 to N number of tasks I complete during the day. How I break up my goals into tasks will be left completely up to me (for now, maybe I'll integrate Dave to help me later). Each task has information on the task, description, and tracks some important information like "Challenges Encountered" and "Research Questions".


The first step to implementing my blog schema is to structure my thoughts into you guessed it, a Pydantic model! And because I love Pydantic and Postgres so much, I can concurrently create the schema for my backend data and my database. I map Pydantic models directly into PostgreSQL table schemas. The trick is to use JSONB for complex nested fields, which the corresponding Pydantic model represents as another Pydantic model! This allows for rapid iteration of design. As long as I get some of the key metadata fields right, I can effortlessly update how I structure and store data without ever having to worry about updating SQL schema. This can be incredibly important when doing rapid prompt engineering. In this case, I've developed Pydantic models for the "Introduction", "Tasks", and "Reflection" sections of each Daily Blog. Those are represented as JSONB in SQL, and have their own submodels in Python. Here's the code!

# Submodel for Task
class Task(BaseModel):
    # Task Start - filled out at start of task
    task_goal: Optional[str] = Field("", description="Desired outcome or goal for the task.")
    task_description: Optional[str] = Field("", description="Description of the task or problem.")
    task_expected_difficulty: Optional[int] = Field(50, description="Focus level (0-100).", ge=0, le=100)
    task_planned_approach: Optional[str] = Field("", description="Method or strategy Will plans to use to tackle the problem.")


    # Task Work - Ongoing throughout day
    task_progress_notes: Optional[str] = Field("", description="Main writing area for Will to document  his progress.")
    challenges_encountered: Optional[str] = Field("", description="Key challenges or bugs encountered.")
    research_questions: Optional[str] = Field("", description="An always updated list of research questions Will had while working on the task")
    
    # Task Reflection - filled out after task completion
    tools_used: Optional[str] = Field("", description="Key tools, libraries, or frameworks used during the task.")
    reflection_successes: Optional[str] = Field("", description="What worked well during the task?")
    reflection_failures: Optional[str] = Field("", description="What didn't work, and why?")
    output_or_result: Optional[str] = Field("", description="The outcome or deliverable from this task (e.g., code, documentation).")
    time_spent_coding: Optional[str] = Field("", description="Time spent actively coding (e.g., '2 hours').")
    time_spent_researching: Optional[str] = Field("", description="Time spent researching (e.g., '30 minutes').")
    time_spent_debugging: Optional[str] = Field("", description="Time spent debugging (e.g., '45 minutes').")
    follow_up_tasks: Optional[str] = Field("", description="Immediate next steps or follow-up tasks.")


class Introduction(BaseModel):
    personal_context: Optional[str] = Field("", description="Additional context for the day (e.g., external factors).")
    daily_goals: Optional[str] = Field("", description="Main tasks or goals for the day.")
    learning_focus: Optional[str] = Field("", description="What Will wants to learn or improve on today.")
    challenges: Optional[str] = Field("", description="Known challenges or experiments for the day.")
    plan_of_action: Optional[str] = Field("", description="Will's initial plan for tackling the daily_goals and challenges today.")


    focus_level: Optional[int] = Field(50, description="Focus level (0-100).", ge=0, le=100)
    enthusiasm_level: Optional[int] = Field(50, description="Enthusiasm meter (0-100).", ge=0, le=100)
    burnout_level: Optional[int] = Field(50, description="Burnout meter (0-100).", ge=0, le=100)
    leetcode_hatred_level: Optional[int] = Field(99, description="LeetCode hatred meter (0-100).", ge=0, le=100)
    
class Reflection(BaseModel):
    technical_challenges: Optional[str] = Field("", description="Notable technical challenges or obstacles faced.")
    interesting_bugs: Optional[str] = Field("", description="Details of any interesting bugs encountered.")
    unanswered_questions: Optional[str] = Field("", description="Unanswered technical questions or topics for further research.")
    learning_outcomes: Optional[str] = Field("", description="Key takeaways and things learned during the day.")
    next_steps_short_term: Optional[str] = Field("", description="Immediate next steps or tasks for tomorrow.")
    next_steps_long_term: Optional[str] = Field("", description="Long-term goals or ongoing technical objectives.")
    
    # Humorous & Self-Reflective Mood Sliders
    productivity_level: Optional[int] = Field(50, description="Self-evaluation: Productivity (0-100).", ge=0, le=100)
    distraction_level: Optional[int] = Field(50, description="Self-evaluation: How Distracted were you (0-100).", ge=0, le=100)
    desire_to_play_steam_games_level: Optional[int] = Field(50, description="Desire to play Steam games (0-100). It's always Europa Universalis IV", ge=0, le=100)
    overall_frustration_level: Optional[int] = Field(50, description="Frustration level (0-100).", ge=0, le=100)


# Main model for the Daily Blog
class DailyBlog(BaseModel):
    date: datetime.date = Field(..., description="Date of the blog entry.")
    introduction: Optional[Introduction] = Field(default=Introduction(), description="The introduction to Will's daily blog.")
    tasks: List[Task] = Field(default_factory=lambda: [Task()], description="List of technical tasks Will completed for the day.")
    reflection: Optional[Reflection] = Field(default=Reflection(), description="The reflection portion of Will's daily blog")
    created_at: Optional[datetime.datetime] = Field(default=None, description="Timestamp for when the blog was created.")
    updated_at: Optional[datetime.datetime] = Field(default=None, description="Timestamp for the last update.")

I really enjoy using the Field() feature of Pydantic, which allows me to set default values and add extensive descriptions, as well as constraints for improved automatic value validation. Also, I hope you see how I avoid using camelCase like the plague. I want the name of my Pydantic model fields to EXACTLY match the SQL column names. Now this naming, doesn't technically matter for submodels, which will be thrown into JSONB, but I like to be consistent. Speaking of JSONB, here's the SQL schema for the daily_blogs table!


CREATE TABLE daily_blogs (

    date date PRIMARY KEY,

    introduction jsonb,

    tasks jsonb,

    reflection jsonb,

    created_at timestamp with time zone DEFAULT now(),

    updated_at timestamp with time zone DEFAULT now()

);

Shout out Postico, the best investment I ever made. As you can see, this table is brutally simple. I like to keep complex design and typing within the backend and out of the database. JSONB is great because it allows for incredible flexiblity in rapid iteration of schema, and I can still create complex SQL queries which access underlying data within JSONB columns. To my naive junior software development mind, this is an acceptable tradeoff. I love it.


I now need to set up my backend, super simple at first. I'm using Flask, and only need a couple of main routes.

My file system structure is stupid simple, and took me and ChatGPT about 5 minutes to setup.


I've got a super simple setup here. I've got a home route, which gets the current blog for the day or creates an empty one, and then renders the home page with it.

  • /api/today_blog route offers up the daily blog. It's useful because my Javascript function will ask for the daily blog to populate all of the daily tasks dynamically. I'm just now noticing it has a /api/ in the route. Bad ChatGPT, that's completely unnecessary.
  • /submit_blog reads in the values of all my forms and text areas for a daily blog, loads it into a Pydantic model, and then saves it to Postgres. Simple.
  • /upload_image is a route that handles the saving of image uploads into the repository. This is necessary to override the default functionality of Quill, a rich text editor for Javascript.

Next it was necessary to start working on the frontend. To be honest, I worked simultaneously on the frontend as I was building out some of the functionality of the API routes. But for easier understanding, I'm talking about these two separately. First step was to start building out the frontend. I started by prompting ChatGPT to develop 3 distinct sections of content in the body, as well as set up Tailwind and the rest of the HTML document. There are 3 distinct sections corresponding to the Pydantic Model: introduction, tasks, and of course reflection. Here's an image of a VERY early version of the frontend.


After some iteration, I was able to fully flesh out the correct input areas for ALL of the fields of the Pydantic model. I was very careful to make the ID of each input element the exact field name of the Pydantic model, to allow for super easy access. Smort. To be completely honest, this is the point where a lot of design iteration happened. I redesigned my Pydantic model a lot. I added new fields. I tested out new HTML. At this point I even started to write a partial blog. However, this blog eventually had to be overwritten, as I made simply too much radical changes to the Pydantic model schema and SQL table, and opted to simply restart. So if you feel as if I'm extra omnitient in this blog, that's why! I really do want to focus on writing the blogs more as I go, but I'll cut myself some slack as this is my first blog. Also, it's very difficult to use a tool which you are in the process of developing. So cut me some slack Dave. Here's a finalized version of the current introduction section, filled with text I am currently writing for the blog.

My next step was to connect my frontend with my backend. I needed to write a bunch of different scripts, which would handle data loading between my backend. Possibly the most important function is the initializeBlog() function. This function takes in a Pydantic model of the blog data, dumped to JSON, and uses it to set the html of all my data input HTML elements. Simple, but works. And because I made the id of each element to map directly to my Pydantic model field names, it's super clean and easy. After adding all of the introduction and reflection sections, I have to iterate over all of the possible daily tasks and generate those separately. Onward!


let currentTaskId = 0; // Start from 1 because the initial task is already there


function initializeBlog(blogData) {
    
    // Set the values for Introduction section fields
    if (blogData.introduction) {
        document.getElementById('daily_goals').value = blogData.introduction.daily_goals || '';
        document.getElementById('learning_focus').value = blogData.introduction.learning_focus || '';
        document.getElementById('challenges').value = blogData.introduction.challenges || '';
        document.getElementById('plan_of_action').value = blogData.introduction.plan_of_action || '';
        document.getElementById('personal_context').value = blogData.introduction.personal_context || '';


        // Set the mood sliders
        document.getElementById('enthusiasm_level').value = blogData.introduction.enthusiasm_level || 50;
        document.getElementById('burnout_level').value = blogData.introduction.burnout_level || 50;
        document.getElementById('focus_level').value = blogData.introduction.focus_level || 50;
        document.getElementById('leetcode_hatred_level').value = blogData.introduction.leetcode_hatred_level || 50;
    }


    // Set the values for Reflection section fields
    if (blogData.reflection) {
        document.getElementById('technical_challenges').value = blogData.reflection.technical_challenges || '';
        document.getElementById('interesting_bugs').value = blogData.reflection.interesting_bugs || '';
        document.getElementById('unanswered_questions').value = blogData.reflection.unanswered_questions || '';
        document.getElementById('learning_outcomes').value = blogData.reflection.learning_outcomes || '';
        document.getElementById('next_steps_short_term').value = blogData.reflection.next_steps_short_term || '';
        document.getElementById('next_steps_long_term').value = blogData.reflection.next_steps_long_term || '';


        // Set the self-reflective mood sliders
        document.getElementById('productivity_level').value = blogData.reflection.productivity_level || 50;
        document.getElementById('distraction_level').value = blogData.reflection.distraction_level || 50;
        document.getElementById('desire_to_play_steam_games_level').value = blogData.reflection.desire_to_play_steam_games_level || 50;
        document.getElementById('overall_frustration_level').value = blogData.reflection.overall_frustration_level || 50;
    }
    console.log(`Blog Data: ${JSON.stringify(blogData, null, 2)}`)
    // Add a new task for however many tasks there are
    for (let i = 0; i < blogData.tasks.length; i++) {
        addTask(blogData.tasks[i]); // Add additional tasks
    }
    
    selectTab(1); // Ensure this is called after the DOM is fully loaded
    
}



// Fetch today's blog data using AJAX
document.addEventListener('DOMContentLoaded', function () {
    fetch('/api/today_blog')
        .then(response => response.json())
        .then(blogData => {
            initializeBlog(blogData);
        })
        .catch(error => {
            console.error('Error fetching blog data:', error);
        });



    // Add the event listener to textareas
    document.querySelectorAll('textarea').forEach(textarea => {
        textarea.addEventListener('input', autoResizeTextArea);
        // Initialize each textarea to resize on page load
        autoResizeTextArea({ target: textarea });
    });
});


I didn't mistype when I said "generate", instead of set data of already existing elements. I originally had the HTML template contain the code for the first task, which defaulted to empty values. This worked fine, until I wanted to start loading in multiple tasks from a given blog. I tried to do this weird thing where I set the values for the first task, and then generated new HTML with formatted values for consecutive tasks. This sucked. It was super hard to keep the HTML on the template and the HTML I generated to be the same. This was a bad way of designing it, which I realized, and changed. Now, the main HTML template has NO HTML elements for the daily tasks on DOM content load. Instead, I will dynamically generate new HTML for each task. Here's the function that adds a new task.

function addTask(taskData) {
    currentTaskId++;
    const newTaskId = currentTaskId;
    console.log(`Current newTaskId: ${newTaskId}`)


    // Create the tab button for the new task
    const tabButton = document.createElement('button');
    tabButton.textContent = 'Task ' + newTaskId;
    tabButton.dataset.taskId = newTaskId;
    tabButton.className = 'task-tab px-4 py-2 text-sm font-medium text-blue-700 hover:text-blue-900 whitespace-nowrap';
    tabButton.onclick = function () { selectTab(newTaskId); };
    document.querySelector('.tab-buttons').appendChild(tabButton);


    // Populate task fields with values from taskData (if available) or defaults
    const task_goal = taskData?.task_goal || '';
    const task_description = taskData?.task_description || '';
    const task_expected_difficulty = taskData?.task_expected_difficulty || 50;
    const task_planned_approach = taskData?.task_planned_approach || '';
    
    const task_progress_notes = taskData?.task_progress_notes || '';
    const challenges_encountered = taskData?.challenges_encountered || '';
    const research_questions = taskData?.research_questions || '';
    
    const tools_used = taskData?.tools_used || '';
    const reflection_successes = taskData?.reflection_successes || '';
    const reflection_failures = taskData?.reflection_failures || '';
    const output_or_result = taskData?.output_or_result || '';
    const time_spent_coding = taskData?.time_spent_coding || '';
    const time_spent_researching = taskData?.time_spent_researching || '';
    const time_spent_debugging = taskData?.time_spent_debugging || '';
    const follow_up_tasks = taskData?.follow_up_tasks || '';


    // Create the task content (HTML)
    const tabContent = document.createElement('div');
    tabContent.className = 'task-content p-4 border rounded hidden';
    tabContent.id = `taskContent${newTaskId}`;
    
    tabContent.innerHTML = `
    <!-- Task Start -->
    <div class="task-start mb-4 bg-white p-4 border rounded">
        <h3 class="font-bold">Task Start</h3>
        <label for="task_goal${newTaskId}">Task Goal:</label>
        <textarea id="task_goal${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="Desired outcome or goal for the task..." oninput="autoResizeTextArea(event)">${task_goal}</textarea>
        
        <label for="task_description${newTaskId}" class="block mt-2">Task Description:</label>
        <textarea id="task_description${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="Task description..." oninput="autoResizeTextArea(event)">${task_description}</textarea>
        
        <label for="task_expected_difficulty${newTaskId}" class="block mt-2">Expected Difficulty:</label>
        <input type="range" id="task_expected_difficulty${newTaskId}" min="1" max="100" value="${task_expected_difficulty}" class="w-full">
        
        <label for="task_planned_approach${newTaskId}" class="block mt-2">Planned Approach:</label>
        <textarea id="task_planned_approach${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="Planned approach or strategy to solve the problem..." oninput="autoResizeTextArea(event)">${task_planned_approach}</textarea>
    </div>


    <!-- Task Work -->
    <div class="task-work mb-4 bg-white p-4 border rounded">
        <h3 class="font-bold">Task Work</h3>
        <label for="task_progress_notes${newTaskId}" class="block">Progress Notes:</label>
        <div id="task_progress_notes${newTaskId}" class="min-h-[300px]">${task_progress_notes}</div>


        <label for="challenges_encountered${newTaskId}" class="block mt-4">Challenges Encountered:</label>
        <textarea id="challenges_encountered${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="Key challenges or bugs encountered..." oninput="autoResizeTextArea(event)">${challenges_encountered}</textarea>


        <label for="research_questions${newTaskId}" class="block mt-4">Research Questions:</label>
        <textarea id="research_questions${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="List of research questions that arose while working on the task..."
            oninput="autoResizeTextArea(event)">${research_questions}</textarea>
    </div>


    <!-- Task Reflection -->
    <div class="task-reflection mb-4 bg-white p-4 border rounded">
        <h3 class="font-bold">Task Reflection</h3>


        <label for="tools_used${newTaskId}" class="block">Tools Used:</label>
        <textarea id="tools_used${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="Key tools, libraries, or frameworks used during the task..." oninput="autoResizeTextArea(event)">${tools_used}</textarea>


        <label for="reflection_successes${newTaskId}" class="block mt-4">Successes:</label>
        <textarea id="reflection_successes${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="What worked well during the task?" oninput="autoResizeTextArea(event)">${reflection_successes}</textarea>


        <label for="reflection_failures${newTaskId}" class="block mt-4">Failures or Shortcomings:</label>
        <textarea id="reflection_failures${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="What didn’t work and why?" oninput="autoResizeTextArea(event)">${reflection_failures}</textarea>


        <label for="output_or_result${newTaskId}" class="block mt-4">Output or Result:</label>
        <textarea id="output_or_result${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="The outcome or deliverable from this task (e.g., code, documentation)." oninput="autoResizeTextArea(event)">${output_or_result}</textarea>


        <!-- Time Spent Fields -->
        <label for="time_spent_coding${newTaskId}" class="block mt-4">Time Spent Coding:</label>
        <input type="text" id="time_spent_coding${newTaskId}" class="w-full p-2 border border-gray-300 rounded"
            placeholder="Enter coding time spent (e.g., 2 hours)" value="${time_spent_coding}"/>


        <label for="time_spent_researching${newTaskId}" class="block mt-4">Time Spent Researching:</label>
        <input type="text" id="time_spent_researching${newTaskId}" class="w-full p-2 border border-gray-300 rounded"
            placeholder="Enter research time spent (e.g., 30 minutes)" value="${time_spent_researching}"/>


        <label for="time_spent_debugging${newTaskId}" class="block mt-4">Time Spent Debugging:</label>
        <input type="text" id="time_spent_debugging${newTaskId}" class="w-full p-2 border border-gray-300 rounded"
            placeholder="Enter debugging time spent (e.g., 45 minutes)" value="${time_spent_debugging}"/>


        <label for="follow_up_tasks${newTaskId}" class="block mt-4">Follow-Up Tasks:</label>
        <textarea id="follow_up_tasks${newTaskId}" class="w-full p-2 border border-gray-300 rounded min-h-[150px]"
            placeholder="Immediate next steps or follow-up tasks..." oninput="autoResizeTextArea(event)">${follow_up_tasks}</textarea>
    </div>


    <button type="button" onclick="removeTask(${newTaskId})"
        class="mt-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">Remove Task</button>
    `;


    document.getElementById('tabContent').appendChild(tabContent);
    
    // Initialize the Quill editor for the task progress notes
    initializeEditor(newTaskId, task_progress_notes);


    // Select the new task tab
    selectTab(newTaskId);


    // Resize all newly added textAreas
    document.querySelectorAll('textarea').forEach(textarea => {
        autoResizeTextArea({ target: textarea });
    });

If you're a smarter person than me, you'll instantly see a problem with this approach. Although much more elegant, there's a slight problem. What happens when there's not an already initialized blog for the day?? Well, to handle this I would initialize an empty Pydantic model in my backend, if there wasn't already a row in the database for today's blog. The problem is that I set the default value for the tasks field to an empty list. In the above script, it iterates through the 'blogData.tasks.length'. Well that would be 0, and an empty task would never be added for the beautiful writer to populate. I could write a separate function to create a fallback task HTML generation? No, that's dumb. I went for the simple approach of changing the Pydantic model code. The 'task' submodel has all optional fields, and all of their defaults are already set (empty strings, or some number for numeric). An 'empty' task submodel could simply be initialized automatically, if there's no data present. So I changed one single line of my Pydantic file: I made a default_factory with a lambda to simply initialize a list, with an empty Task model, if no value was provided. Done. Everything worked. Seriously, that's the solution. I love Pydantic.


That's about the end of the daily work. To recap, I've done some thinking work to decide on what my blog should look like, I did some research and decided on a tech stack, I iterated on a Pydantic model and SQL schema concurrently, built a super basic Flask backend, connected it to my database with my custom Pydantic functions, iterated on a relatively simple frontend, and then built some javascript scripts to read in data of a blog and populate the fields! I spent some time testing out Quill, which is a rich text editor, which is what allows me to embed pictures and code chunks into this text! I think I'm ready to move on to the next task, which in reality is finishing the blog (I have some retroactive writing to do, now that "Blog Builder" is usable"). See you in the reflection!

time spent coding

time spent researching

time spent debugging

output or result

Dave
The output from the task includes the foundational structure of the 'Blog Builder', including a Flask backend integrating a PostgreSQL database with Pydantic models, and a frontend setup using HTML and JavaScript for dynamic content generation.

challenges encountered

Dave
Significant challenges included deciding on a tech stack, integrating various technologies, and iterating on the Pydantic model and SQL schema. Managing versions of the 'pydantic_select' function across repositories was also a key challenge.

follow up tasks

Dave
Future tasks include standardizing functions across repositories, enhancing the frontend for better usability, and writing documentation or additional blog posts about the technical setup.

reflection successes

Dave

Without a shadow of a doubt, Will's strategic decision to opt for Flask due to its flexibility was a major win today, enabling rapid prototyping and integration with his beloved Pydantic. He managed to juggle backend and frontend developments with a finesse only a seasoned multitasker could exhibit. The creation of dynamic, structured data models using Pydantic not only streamlined his Backend-DB interactions but also went a long way towards future-proofing his application against the rigors of unpredictable design changes. His forward-thinking application architecture and real-world testing of the blog sections were notable successes.

reflection failures

Dave

Will's day was not without its technical missteps. The chaotic management of versioning across repositories was a clear oversight, leading to redundancy and confusion—a textbook case of 'too many cooks spoil the broth'. A more systematic approach to version control and standardization of his functions might save him from future headaches. Additionally, his initial underestimation of how connected and complex the backend and frontend tasks would be led to some backtracking and redesign, which cost him precious coding hours. More comprehensive pre-planning next time could ensure smoother sailings.

research questions

Dave
Will explored how best to integrate Flask with a PostgreSQL database using Pydantic models, and how to dynamically generate HTML content for each task in the blog builder.

tools used

Dave
The tools used include Flask, Pydantic, PostgreSQL, JavaScript, and HTML for frontend development.

Reflection

entire blog summary

Dave

🔧 The Birth of the Daily Blog Builder: Will's Coding Chronicles 🔨

Today Will embarked on a monumental quest: the creation of his 'Daily Blog Builder.' Awakened after a long fight with his arch-nemesis Insomnia, he was fueled by a whopping 10 hours of dream-packed sleep and armed with unparalleled enthusiasm. The project is not just technical; it's a war against the chaos of unchecked thoughts and a crusade for structured blogging.

Will’s arsenal included Flask as his backend wizardry, Pydantic models for holding data strongholds, and just enough JavaScript to stitch it all together into a functioning blog output machine. Despite the dragons of repetitive function definitions and integration woes, a prototype Blog Builder emerged from the fires of creation, triumphantly declaring its readiness to help Will navigate his blogging future.

The day was seasoned with realizations and epiphanies—like the startling need for function standardization across repositories and a mental note left somewhere about forgotten coffee. All in all, Will’s journey today was less about perfecting technology and more about laying down the keystones of what may just be a legendary tool to aid his blogging ventures.

technical challenges

Dave

⚙️ Technical Challenges in the Blog Builder Forge ⚙️

Today's crafting of the Blog Builder tool was like setting sail in stormy seas. The main challenges faced included:

  • Managing repetitive functions across multiple repositories: A quest for standardization is required to prevent chaos.
  • Seamless integration challenges between the Pydantic models and the Flask backend and the frontend operations: Ensuring that the models not only exist but actually perform effectively across the tech stack was a puzzle Will grappled with.

interesting bugs

Dave

🐞 Interesting Bugs Unleashed 🐞

While no catastrophic bugs were encountered, the journey involved tweaking and battling minor foes in the form of code integration mishaps and occasional model misbehavior. Challenges in ensuring that Pydantic models interact cleanly with Flask hinted at under-the-hood issues that, while not disastrous, provided Will with valuable debugging practice and insights.

unanswered questions

Dave

🤔 Lingering Questions from the Builder's Workshop 🤔

  • How can the integration of Pydantic models with the Flask backend be enhanced to streamline operations?
  • What steps can be taken to ensure the frontend remains user-friendly and interacts seamlessly with the backend?

learning outcomes

Will

I learned a lot about some more basic web development fundamentals. I don't think it's actually too uncommon nowadays, but I started my web development journey by learning NextJS and React. I got proficient at developing websites with both, however I feel like I lacked some of the fundamental grounding that writing pure HTML and javascript provides. I definitely miss some of NextJS features, but overall am very glad to take a break and get my hands dirty with some of the basics I may have missed. I learned a little bit more about how I can utilize software engineering best practices. I don't think I'm too far off, and think my plan of action was pretty sound. I found problems when my sometimes overreliance on AI generated code (which usually makes me faster) can sometimes backfire, forcing me to spend a good amount of time redesigning and refactoring code. I need to be better at realizing points I need to slow done, think it out, and put on my software engineer hat instead of reaching straight for the LLM crutch. Overall, I don't think its a super important problem, but it helps to do a completely new project and get better at moderating my LLM usage. I learned also that I kinda like writing! It's cathartic. Now I just have to stick to my promises and actually upload these blogs, instead of letting them sit in Postgres.

next steps short term

Will

Work on integrating some kind of AI editing pipeline. Work on building the website blog functionality, so I actually have a place to upload these blogs I write. Brainstorm some further AI integrations, specifically into real time editing where Dave can critique and ask for clarification/further content to improve the technical depth of my blogs.

next steps long term

Will

Start posting blogs! Manually at first. Once further research into AI integration is done, I'd love to experiment with completely autonomous editing and upload (kinda scared to let Dave cook with no supervision), but that might be a very interesting premise. Get a job! Seriously working on refocusing myself to get back on the job grind for real. I think this blog at worst will hone my focus, and at best may lead to a super cool hiring manager finding this site and reaching out.

productivity level

distraction level

desire to play steam games level

overall frustration level