Development – Decoding https://decoding.io Articles about productivity and technology. Tue, 26 Aug 2025 15:54:30 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 142045116 Solving Token Waste with Claude Code Personas https://decoding.io/2025/08/solving-token-waste-with-claude-code-personas/ https://decoding.io/2025/08/solving-token-waste-with-claude-code-personas/#respond Tue, 26 Aug 2025 15:54:30 +0000 https://decoding.io/?p=3468

The new /context command in Claude Code revealed how I’m wasting tokens by having all MCP (Model Context Protocol) tools loaded into one session for every conversation. When I’m in the middle of development, I don’t need OmniFocus task management or DEVONthink database tools.

Based on subagents I had the following idea: what if I split Claude into specialized personas, each loading only the tools it actually needs?

Creating Focused Personas

Instead of one bloated agent trying to do everything, I created three specialized personas:

Main Persona (cc): The clean, focused default. Just filesystem, bash, and macOS automation via one MCP server. This handles most of my daily coding tasks with minimal tool overhead.

Research Persona (ccr): The deep investigation specialist. Loads Zen for multi-model reasoning (GPT-5, O3, Gemini Pro), Context7 for library docs, Octocode for code search and analysis, and Codex for deep code research. When I need to debug something complex or review architecture, this persona has the tools.

GTD Persona (ccg): The productivity workflow expert. Connects to OmniFocus for tasks, Obsidian for notes, DEVONthink for documents, and HyperContext for calendar management. This persona focuses on productivity workflows and task management.

Each persona also has access to subagents for specialized tasks, adding another layer of capability without wasting more tokens.

The implementation lives in my dotfiles as simple shell functions:

# Main persona - clean and fast
cc() {
  claude ${*} --dangerously-skip-permissions
}

# Research persona with specialized tools
ccr() {
  claude ${*} --dangerously-skip-permissions \
    --model opus \
    --mcp-config ~/.claude/mcp-research.json \
    --append-system-prompt "You are a code research and analysis specialist \
focused on deep technical investigation, multi-model reasoning, and \
comprehensive code review. Your expertise includes security auditing, \
architecture analysis, debugging complex issues, and researching best \
practices across codebases. You leverage multiple AI models for consensus \
building, use Context7 to fetch up-to-date library documentation, analyze \
GitHub repositories for patterns with Octocode, and generate thorough \
technical documentation."
}

# GTD persona with productivity tools
ccg() {
  claude ${*} --dangerously-skip-permissions \
    --model opus \
    --mcp-config ~/.claude/mcp-gtd.json \
    --append-system-prompt "You are a GTD specialist orchestrating a \
multi-layered knowledge system where OmniFocus drives project execution, \
Obsidian captures notes and knowledge, and DEVONthink archives reference \
materials. You excel at processing inboxes across all three systems, \
organizing projects with proper next actions, capturing meeting notes \
with task extraction, and maintaining a trusted system for all personal \
and professional information. Your expertise includes creating bidirectional \
links between systems using Hook and maintaining clear separation between \
active project work and archived reference materials."
}

Each persona loads its own MCP configuration file, containing only the relevant tool servers. The --append-system-prompt flag gives each persona a specialized identity and deep expertise in their domain.

The Delegation Pattern

The most powerful aspect isn’t just the separation. It’s the ability for personas to delegate to each other. This creates a network of specialized expertise without loading unnecessary tools.

When delegation happens, I must approve it first. The main persona will say something like: “I’d like to ask the research persona to analyze this Rails controller for N+1 queries using its specialized tools.” Once I type “approved” or “go”, the delegation proceeds.

The delegation always runs in the background using the Bash tool:

// How the main persona actually delegates
Bash({
  command: 'ccr -p "Analyze this Rails controller for N+1 queries and performance issues"',
  description: "Ask research persona to analyze Rails performance",
  run_in_background: true
})

This pattern works both ways. I can also delegate GTD tasks with a simple OmniFocus link:

# Ask GTD persona for comprehensive project review
ccg -p "/gtd:project-review omnifocus:///task/jP9S4CFgPin"

The GTD persona doesn’t just fetch tasks. It discovers linked resources through Hookmark, pulls references and meeting notes from DEVONthink, reads project plans from Obsidian, builds a complete timeline, and suggests concrete next actions based on the full project context. All while the main persona continues with development work.

The background execution is key. The delegating persona doesn’t block waiting for results. It can continue working on other aspects while the specialized persona handles its domain-specific task.

What Changed

The token savings are significant, but the real benefit is focus. Each persona does one thing well with the right tools for that job. The system prompts shaped distinct behaviors. The main persona stays lean and fast. The research persona leverages multiple AI models for deep analysis. The GTD persona navigates my entire productivity stack.

]]>
https://decoding.io/2025/08/solving-token-waste-with-claude-code-personas/feed/ 0 3468
Bookmarked “I Shipped a macOS App Built Entirely by Claude Code” https://decoding.io/2025/07/bookmarked-i-shipped-a-macos-app-built-entirely-by-claude-code/ https://decoding.io/2025/07/bookmarked-i-shipped-a-macos-app-built-entirely-by-claude-code/#respond Mon, 07 Jul 2025 06:47:31 +0000 https://decoding.io/?p=3439

I recently shipped Context, a native macOS app for debugging MCP servers. The goal was to build a useful developer tool that feels at home on the platform, powered by Apple’s SwiftUI framework. I’ve been building software for the Mac since 2008, but this time was different: Context was almost 100% built by Claude Code. There is still skill and iteration involved in helping Claude build software, but of the 20,000 lines of code in this project, I estimate that I wrote less than 1,000 lines by hand.

Indragie shipped a native macOS app with Claude Code writing 95% of the code. Haven’t dug into the full post yet, but Simon Willison pulled out the key bits – Claude nails SwiftUI but gets tripped up by Swift’s newer concurrency stuff.

]]>
https://decoding.io/2025/07/bookmarked-i-shipped-a-macos-app-built-entirely-by-claude-code/feed/ 0 3439
Starting Pezeta https://decoding.io/2025/07/starting-pezeta/ https://decoding.io/2025/07/starting-pezeta/#respond Sun, 06 Jul 2025 23:26:33 +0000 https://decoding.io/?p=3437

Finally decided to start building Pezeta today. It’s been in my mind for years – since we started Agyvihar, our Hungarian podcast about personal finance and productivity.

What’s Pezeta?

Simple: a budgeting app that tells you one number – how much you can spend right now.

That’s it.

No charts. No categories. No endless reports. Just open the app and see: $47. Or $134. Or whatever you can actually spend without screwing up your budget.

The features (keeping it minimal)

The One Number – Your daily spending limit. Updated in real-time. Factors in your bills, savings, everything.

Goals – Want to save $2000 by December? Cool. The app adjusts your daily number automatically. No spreadsheets required.

That’s basically it. If budgeting were simpler, maybe more people would actually do it.

Starting with Rails

I’m not jumping straight into building native apps. Starting with a Rails MVP instead.

Few reasons:

  • Rails 8 has some new stuff I want to try
  • Need a reference implementation anyway for the iOS/macOS versions
  • I can actually use it myself while building

This Rails version won’t go public. It’s just for me to validate the idea and work out the kinks.

Building in public (might regret this)

I’m putting it all out there because watching someone figure things out might be more useful than another perfect tutorial.

Partly for feedback – maybe someone will tell me this is a terrible idea before I waste six months on it.

Partly for accountability – harder to quit when people are watching.

Partly because I’m curious what happens when you share the messy process instead of just the polished result.

The plan

Starting with Rails 8 setup, basic models, authentication. The boring foundation stuff that nobody talks about but takes forever.

After that, we’ll see. Building software is weird.

]]>
https://decoding.io/2025/07/starting-pezeta/feed/ 0 3437
Read “Programmers are modern-day computers” https://decoding.io/2025/02/read-programmers-are-modern-day-computers/ https://decoding.io/2025/02/read-programmers-are-modern-day-computers/#respond Wed, 05 Feb 2025 07:15:06 +0000 https://decoding.io/?p=3368

Programming as we know it is in a process of dying out.

Most haven’t come to terms with it yet.

I’m just saving this, so I can look back at it in 3 years and see if it’s becoming true or not.

]]>
https://decoding.io/2025/02/read-programmers-are-modern-day-computers/feed/ 0 3368
Bookmarked “Developer Roadmaps – roadmap.sh” https://decoding.io/2025/01/bookmarked-developer-roadmaps-roadmap-sh/ Wed, 15 Jan 2025 06:49:24 +0000 https://decoding.io/?p=3250

roadmap.sh is a community effort to create roadmaps, guides and other educational content to help guide developers in picking up a path and guide their learnings.

Role-based and skill-based roadmaps, project ideas, best practices, and other resources for various areas of development including Frontend, Backend, DevOps, Full Stack, AI, and more.

]]>
3250
https://decoding.io/2025/01/3235/ https://decoding.io/2025/01/3235/#comments Sun, 12 Jan 2025 23:50:06 +0000 https://decoding.io/?p=3235

Looks like 37signals is building a new editor called House (MD). The code is already available in the Writebook project which I haven’t tried yet.

This new editor is based on Markdown, which I like, since their current Trix editor is WYSIWYG, although it works pretty well in my experience.

]]>
https://decoding.io/2025/01/3235/feed/ 2 3235
Read “The Pull Request – Tyler Cipriani” https://decoding.io/2024/09/read-the-pull-request-tyler-cipriani/ https://decoding.io/2024/09/read-the-pull-request-tyler-cipriani/#respond Sun, 01 Sep 2024 07:29:11 +0000 https://decoding.io/?p=2930

In 2008, GitHub’s developers could have opted to use git format-patch instead of teaching the world to juggle branches. Or they might have chosen to generate pull requests using the git request-pull command that’s existed in Git since 2005 and is still used by the Linux kernel maintainers today2.

I’m getting into stacked branches these day, but I should look into git request-pull too.

]]>
https://decoding.io/2024/09/read-the-pull-request-tyler-cipriani/feed/ 0 2930
Open-sourcing thinking https://decoding.io/2024/03/open-sourcing-thinking/ https://decoding.io/2024/03/open-sourcing-thinking/#comments Sat, 16 Mar 2024 07:38:06 +0000 https://decoding.io/?p=2807

I write a lot every day, but I don’t publish a lot of it since I have this fear of being judged. But I have a lot of ideas and thoughts that can be boring for some people but maybe interesting for others.

I ramble about tools and workflows in Day One and my Zettelkasten. Those are private posts that are not necessarily useful to anyone, but I want to publish them anyway because I can see a history of my thoughts, which can give someone else an idea.

That’s what #DigitalGarden is about, but it is still different. Sometimes, I want to write a journal entry and be done with it. I don’t need a full-fledged Zettelkasten all the time.

Actually, I’m afraid of publishing these ideas, but as we have had success with open-source software in the last couple of decades, maybe open-source thinking can be a helpful thing to master, too.

What do I mean by that? Blogposts without much crafting and maybe even with a lot of grammar errors, but the idea is that I can write about something, then continue thinking about that idea in a new post, and so on. Maybe I’ll run into a conclusion and come up with something cool or ignore the whole thing at the end, but the critical point here is that I should flex my writing more, and my blog is still the best place for that.

Inspired by More people should write

]]>
https://decoding.io/2024/03/open-sourcing-thinking/feed/ 3 2807
Bookmarked “Ruby on Mac: Get Ruby working on your Mac in minutes with a single command.” https://decoding.io/2024/02/bookmarked-ruby-on-mac-get-ruby-working-on-your-mac-in-minutes-with-a-single-command/ https://decoding.io/2024/02/bookmarked-ruby-on-mac-get-ruby-working-on-your-mac-in-minutes-with-a-single-command/#respond Thu, 22 Feb 2024 08:53:18 +0000 https://decoding.io/?p=2791

I am still not sure if I need this or not, but interesting nonetheless.

]]>
https://decoding.io/2024/02/bookmarked-ruby-on-mac-get-ruby-working-on-your-mac-in-minutes-with-a-single-command/feed/ 0 2791
Sonar – Mac App for GitHub/GitLab Issues https://decoding.io/2024/02/sonar-mac-app-for-github-gitlab-issues/ https://decoding.io/2024/02/sonar-mac-app-for-github-gitlab-issues/#comments Tue, 20 Feb 2024 22:57:15 +0000 https://decoding.io/?p=2785

Sonar is a beatiful (and native) Mac app for managing Github and GitLab issues. I used the beta in the last couple of weeks for managing GitHub issues and it’s really good. The high-level outline view changes how you manage issues.

If you use Hookmark, I also made a integration scripts for it which is pretty useful for linking OmniFocus/Things projects to issues in Sonar.

]]>
https://decoding.io/2024/02/sonar-mac-app-for-github-gitlab-issues/feed/ 3 2785
Bookmarked “Sonar – Native App for GitHub/GitLab Issues” https://decoding.io/2023/12/bookmarked-sonar-native-app-for-github-gitlab-issues/ https://decoding.io/2023/12/bookmarked-sonar-native-app-for-github-gitlab-issues/#respond Sun, 17 Dec 2023 07:05:13 +0000 https://decoding.io/?p=2673

Sonar is a new native Mac app for viewing and editing GitHub/GitLab issues. It’s lightning fast and stores your tasks locally so viewing, searching, and editing is instant (even offline).

I was looking for something like this for a while now.

]]>
https://decoding.io/2023/12/bookmarked-sonar-native-app-for-github-gitlab-issues/feed/ 0 2673
https://decoding.io/2023/12/2644/ https://decoding.io/2023/12/2644/#respond Sat, 09 Dec 2023 09:10:42 +0000 https://decoding.io/?p=2644

Interesting to see how people are using Org mode for journaling.

This is app uses text views to convert the text back to Org mode syntax: kind of a two way street implementation of having a UITextView with a controller which parses and creates rich-text ↔ plain-text.

]]>
https://decoding.io/2023/12/2644/feed/ 0 2644
Read “How I build a feature” https://decoding.io/2023/12/read-how-i-build-a-feature/ https://decoding.io/2023/12/read-how-i-build-a-feature/#respond Fri, 08 Dec 2023 17:58:51 +0000 https://decoding.io/?p=2633

There are a lot of good Git workflow patterns here.

]]>
https://decoding.io/2023/12/read-how-i-build-a-feature/feed/ 0 2633
Bookmarked “You don’t need JavaScript for that – HTMHell” https://decoding.io/2023/12/bookmarked-you-dont-need-javascript-for-that-htmhell/ https://decoding.io/2023/12/bookmarked-you-dont-need-javascript-for-that-htmhell/#respond Sun, 03 Dec 2023 19:17:41 +0000 https://decoding.io/?p=2567

Just because you know something needs JavaScript, doesn’t mean it still does. You can make better websites if you test those assumptions every now and then.

]]>
https://decoding.io/2023/12/bookmarked-you-dont-need-javascript-for-that-htmhell/feed/ 0 2567
Read “Spreadsheets and Small Software – nilenso blog” https://decoding.io/2023/11/read-spreadsheets-and-small-software-nilenso-blog/ https://decoding.io/2023/11/read-spreadsheets-and-small-software-nilenso-blog/#respond Mon, 27 Nov 2023 06:58:06 +0000 https://decoding.io/?p=2494

These sheets are like regular programs in many ways. Felienne Hermans, a veteran spreadsheet researcher, puts it very simply: Spreadsheets are code. She also goes on to show that they suffer from the same problems as real software.

]]>
https://decoding.io/2023/11/read-spreadsheets-and-small-software-nilenso-blog/feed/ 0 2494
The myth and reality of Mac OS X Snow Leopard https://decoding.io/2023/11/the-myth-and-reality-of-mac-os-x-snow-leopard/ https://decoding.io/2023/11/the-myth-and-reality-of-mac-os-x-snow-leopard/#comments Fri, 17 Nov 2023 21:06:45 +0000 https://decoding.io/?p=2243

I agree with this. We would be better off with a more “natural” update cycle on the Mac. It’s a slower-moving platform these days anyway:

Regardless of the motivation, the annual updates are more of a burden than a blessing to many Apple customers, including myself. I wish that Apple would drop the artificial schedule and let the major updates come more naturally. This isn’t just the attitude of a developer and so-called “power user”. Many “normal” users”, the proverbial moms, feel the same way. Actually, my literal mom told me she doesn’t like the ceaseless annual major updates either. She’s learned from hard experience that they’re not necessarily safe to install. Major updates can be very disruptive, creating new problems and wrecking old workflows. The press is always excited by major updates, because they give the press a lot to write about, but the public is not as sanguine. We occasionally need a break of 23 months, or more, from computing disruption. That would be another Snow Leopard.

Related to this: The Mac, The Myth, The Legend: How Snow Leopard became synonymous with reliability

]]>
https://decoding.io/2023/11/the-myth-and-reality-of-mac-os-x-snow-leopard/feed/ 2 2243
https://decoding.io/2023/02/scarlet-on-the-app-store/ https://decoding.io/2023/02/scarlet-on-the-app-store/#comments Sat, 18 Feb 2023 11:10:40 +0000 https://decoding.io/?p=1981

Bookmarked “Scarlet“:

Scarlet is a personal issue tracker that saves to a file that you can include in your project directory, or anywhere you prefer. No accounts, no cloud services, no syncing, no third-party integration. Just a simple place to file away your project’s to-dos and close them when they’re complete.

]]>
https://decoding.io/2023/02/scarlet-on-the-app-store/feed/ 1 1981
https://decoding.io/2023/02/1975/ https://decoding.io/2023/02/1975/#respond Wed, 08 Feb 2023 11:08:20 +0000 https://decoding.io/?p=1975

Other than Apple’s official documentation, what are the best sources on learning AppKit?

]]>
https://decoding.io/2023/02/1975/feed/ 0 1975
Tweaking workflows https://decoding.io/2022/12/1723/ https://decoding.io/2022/12/1723/#comments Mon, 26 Dec 2022 09:09:00 +0000 https://decoding.io/?p=1723
  • I constantly tweak my workflows this time of the year. I usually provide tools for others—that’s what I do for a living—but I also have to keep my knives sharp.

  • This year I’m tweaking two things.

  • Take better notes while watching a video.

  • Moving my Zettelkasten over to Bike.

    • I like to think about my Zettelkasten being a large outline. Keeping it inside Bike could be beneficial.

      • I’m trying to mimic the analog Zettelkasten (or Antinet).

        • I won’t use an analog one since I like the digital one’s benefits better, but I also want ideas from the analog one.

        • I’m a programmer and I use my Zettelkasten to understand coding concepts. I have some code snippets stored in SnippetsLab, so it’s easier to link to those from my Zettelkasten outline than keeping them on paper.

      • I can use the classical Luhmann IDs to add an ID each note.

      • I can nest notes under each other.

        • I can easily link notes together thanks to the Bike and Hook integration.

    • Disadvantages

      • I don’t have backlinks, but I’m not sure I need that inside a Zettelkasten.

]]>
https://decoding.io/2022/12/1723/feed/ 2 1723
https://decoding.io/2022/03/1536/ https://decoding.io/2022/03/1536/#respond Fri, 04 Mar 2022 13:36:23 +0000 https://decoding.io/?p=1536

I built a completely responsive 12-6-4 columns grid component built on top of GitHub’s ViewComponent gem and the CSS Grid Layout.

Grid component preview html erb

Here’s a demo:

]]>
https://decoding.io/2022/03/1536/feed/ 0 1536
How I Built a Date Picker https://decoding.io/2022/01/how-i-built-a-date-picker/ https://decoding.io/2022/01/how-i-built-a-date-picker/#comments Mon, 24 Jan 2022 21:28:51 +0000 https://decoding.io/?p=1486

Entering date and time values into an application is hard. I combined these two interactions into one UI to make things easier. Here’s how I did it.


How the date picker works

On a simple scale, a date picker is just a table with six rows and seven columns. As a skeleton for the calendar, we have to fill this table with dates using rows and columns as coordinates for each day.

With this in mind, we can already assume some essential bits.

  1. Some months spans across six weeks, so we need six rows.
  2. Columns represent days with a fixed order, so Monday is anchored to 0, Tuesday to 1, Wednesday to 2, etc.
  3. We can go back and forth between months just by adding and subtracting the current month offset.
  4. We're always working with two dates: one currently selected, and proposing another one as a new value for the input field. These dates can be on the same day, but we're not concerned with that.

These are the basic building blocks of our date picker, so we can see how these pieces fit together with a little bit of diagramming.

Initial sketch of the date picker.

The date picker is using three layers of models.

  1. The HTML view draws the rows of columns of the calendar skeleton and some UI parts.
  2. The state/controller layer changes the date, proposed date, and month offset values responding to events—also parsing dates.
  3. The renderer, which is a reactive layer, watching state changes and echoing each day of the subjected month into the correct row and column coordinate. Also showing or hiding remaining empty week rows.

A quick word about popovers

We will base the date picker on another Stimulus controller, which acts as a popover. This controller's task is to attach itself to a button or link and toggle its target box on and off. The date picker is also acting as a popover, so instead of building it into the date picker controller, we can extract this behavior and use it separately. It's also a great example of how multiple Stimulus controllers can work together.

The view

The date picker will be a part of a Rails app, so we will use its features to render the view part without any fuss.

First, there is a shared partial under app/views/shared/_date_picker.html, which contains the required HTML. It has a couple of jobs.

  1. Since there is a separate Stimulus controller for the popover functionality, the view should generate a random ID property for each date picker. Having different IDs allows us to have more than one date picker instance on the same page and we’re toggling the right one on or off.
  2. It creates a text_field to attach the date picker to it in the parent form.
  3. This partial also maps Stimulus targets and events to each controller.
  4. Since day names are not going to change in this example, the partial can prerender a table header so that day names can come from the global I18n localizations.
  5. For the calendar skeleton, we have to render an empty table body for six rows and seven columns.
  6. The UI also needs two additional hour and minute fields to change the time.

The other part of the view is a custom FormBuilder class, making two new methods available in a form_for block. The date_picker_field and date_time_picker_field are simple methods rendering the _date_picker.html.erb partial into any form. This way, we can seamlessly use the date picker like any other Rails form helpers.

To make our job a bit easier, we can create an additional global helper method called record_form_for, which can be used in place of the standard form_for to make our customized FormBuilder subclass available everywhere in the view.

Styling

As I mentioned before, we will show the date picker in a popover, so we should base its styling on the popover base class. We can do this by extending the base .popover CSS class with a .date_picker_popover to target all date picker specific CSS in a custom popover class.

Our date picker has styles for the navigation buttons, the day names header, the calendar view, and the optional time picker fields. Also, it overrides the base .show class to show the popover with a custom animation.

The base .date_picker_popover class is also scaled down by 0.825 using the transform attribute for desktop browsers because there we have more precise mouse-based input. We scale back to the original size on touch devices to have bigger tap targets.

Comparison of the date picker scaling: macOS on the left and iPadOS on the right.
Comparison of the date picker scaling: macOS on the left and iPadOS on the right.

The Stimulus controller

Our controller has two distinct layers separated into two pieces. One is the renderer, which uses a JavaScript Proxy object; the other is the controller itself, which has a state object and a couple of methods that respond to events and modify properties on the state. The renderer part watches these changes and updates the UI reactively.

Let’s start with the first piece. On a higher level, this is our renderer without any implementation code. The first part sets attribute values on the state object; the second part runs the UI manipulation code responding to those changes.

renderer = {
  set: (target, property, value) => {
    // Set the value of the state object then respond to those changes down below.
    target[property] = value

    if (property === 'proposedDate') {
      console.log('Render the calendar and optionally set the date.')
    }

    if (property === 'date') {
      console.log('Set the value of the date field')
    }

    if (property === 'month') {
      console.log('Render the calendar responding to month changes.')
    }

    if (property === 'started' && value === false) {
      console.log('Dismiss the UI.')
    }

    return true
  }
}

The code example above is a compelling way to manipulate the UI. We can set a couple of values on the state which gets updated by the controller, but we extracted our UI manipulation code into a renderer. You can run wild and create an entirely separate module for the renderer, you can even have multiple renderers, one for a desktop and one for mobile, but I like to keep them in the controller since we have access to everything in it via this as well.

To use the renderer, we have to proxy it through the state object. The best place for this is in the controller connect method, which runs when Stimulus connects the controller to the HTML view.

connect() {
  this.state = new Proxy({}, this.renderer)

  this.dateFieldTarget.readOnly = this.isTouch
  this.timePickerTarget.style.display = this.isTimeActive ? 'flex' : 'none'
}

You may also notice that other parts of the code deal with displaying the time picker fields and changing attributes on the date field. Why not move these pieces into the renderer?

Since these changes happen once, it’s better to keep them in the connect method. Sure we can extract them into state properties, but since we don’t update them anymore in the controller’s lifecycle, it’s easier to deal with them right here. Having this flexibility is why Stimulus is so awesome. You don’t have to deal with a predefined reactive state like in other frameworks; you can create your own if you want using native JavaScript features, but you can also update the DOM directly from anywhere if that fits your code better.

Now let’s talk about the most critical parts of the controller’s skeleton.

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [
    // ...
  ]

  currentMonth(event) { 
    // ... 
  }

  nextMonth(event) {
    // ... 
  }

  previousMonth() { 
    // ... 
  }

  pickDate(event) { 
    // ... 
  }

  start(event) { 
    // ... 
  }

  parseDate(event) { 
    // ... 
  }

  dismiss(event) { 
    // ... 
  }

  getCoordinates(date) { 
    // ... 
  }
}
  • currentMonth: Resets the state.date to the current date and state.month to 0. The method above gets called when we press the Today button.
  • nextMonth: Goes to the next month by adding 1 to the state.month when we press the right arrow on the top of the date picker.
  • previousMonth: It does the opposite of its counterpart and goes to the previous month by subtracting one from the state.month. It also gets called when we press the left arrow on the top of the date picker.
  • pickDate(event): Handles the click event on a picked date from the calendar and set the value of the dateFieldTarget. The data-action="click->date-picker#pickDate" isn’t in the HTML view; instead, it added dynamically by the renderer part. The good thing is that Stimulus picks up these data attributes automatically, so dynamic event handling works without doing anything special on our part.
  • start: Gets called when we focus on the date field so the date picker can start its whole date parsing and rendering process.
  • parseDate: The date parser part processes the value of dateFieldTarget using Chrono and sets the state.proposedDate, which gets highlighted by the renderer as a possible date or date-time value for the dateFieldTarget. It is very flexible, accepts input from the dateFieldTarget like “tomorrow evening” or “next month”.
  • dismiss: There are multiple ways in the flow where the date and time picking interaction ends, like pressing Return in one of the time picker input fields, selecting a date from the calendar, or using the natural language parser. Since we’re having a separate controller for the popover state, this method sets the state.started value to false so the renderer can hide the date picker using the PopoverToggleController.
  • getCoordinates: Gets called on each date of the current month by the renderer to determine which row and column it should modify in the DOM when the calendar gets filled with dates. It returns an object like { row: 1, column: 4 }.

We also have some other helpers in the controller, but the main logic runs in the methods listed above. The cool thing is that there is no DOM manipulation code here; we change a couple of values on the state, do some date and time calculation using Chrono and Moment.js, but that’s it. It’s simple and easy to understand.


UI behaviors

A date picker seems a simple component, but it has a surprisingly complex behavior set. It’s essential to get this right because picking a date isn't a fun thing to do. Our date picker should get this job done quickly then get out of the way.

And it has to work in multiple ways to remain flexible for a different type of people. Let's see how these behaviors can work together to add up as a flexible tool.

1. Animations

Animations used right can bring our attention to something, surprise us, or even bring a bit of joy to an otherwise dull task.

We attached our date picker to an input field, so in our example, we mark these fields with a calendar icon to indicate what type of data we’re expecting. It makes sense to use this icon as an anchor point for our animation. The date picker grows out from the left side of the input field when we click on it, indicating that it has to do something with the calendar.

When the animation finishes, there is a small bounce, which gives a bit of playfulness to the UI.

2. 2-way data update

We have two types of input methods for the date picker.

  1. Typing values in the date field.
  2. Selecting dates in the calendar.

These two have to morph together and be connected. This connection needs to happen in real-time, so it doesn’t matter if we’re typing something in the field or selecting a date in the calendar. We should see changes immediately without switching modes or waiting for something to finish.

3. Click-away date parsing

When people are dealing with popovers, they usually try to exit them by clicking away. We should respect this behavior and remember that we’re dealing with formatted data, so we shouldn’t just leave the raw values in the field. When the user blurs out from the date field, we still have a chance to do a final date parsing to have valid data in our input field. We can give something valuable to the user, even when she wants to exit from the interaction.

4. Keyboard-only access

Power users are very good with keyboards; you can spot them when you see somebody signing up for your service. They give you clues like using the Tab key to switch fields because they want data input interactions to be quick and painless. We can always do more to speed things up for them.

In the case of a date picker, natural language parsing can be robust. We integrated it into the date field, and because of the two-way data binding, you can see it working in realtime. The calendar switches months as you type in a date. Hours and minutes filled into their respective fields.

Everything works from the keyboard. Switching away from the field triggers the click-away date parser mentioned above, so you can press Tab (or Return) and get the correctly formatted date every time.

5. Mouse-only access

In contrast, novice people not accompanied using just the keyboard. Having the date and time picker available as a WYSIWYG-like interface is the whole point of creating a unique controller for this type of field.

Switching months, selecting the date, and adjusting the time can be done via the mouse—no need to type anything. And the 2-way data update also gives instant feedback with the final output.

It’s a great place to mention again that our UI scaling takes the input method into account. On touch devices, the date picker scales up to use the calendar with our fingers without feeling cramped.


Try it for yourself

Everything we discussed in this article is also available on GitHub. In our example, we use a simple post model where the date picker is attached to the published date field.

The repository is a simple Rails app without any external dependencies (it uses SQLite for its database), so setting it up should be very easy. All you have to do is to install Rails using this guide, then run rails db:setup and rails server to get it running.

Checkout this project on GitHub


]]>
https://decoding.io/2022/01/how-i-built-a-date-picker/feed/ 28 1486
Remote work is not local work at a distance https://decoding.io/2021/02/remote-work-is-not-local-work-at-a-distance/ https://decoding.io/2021/02/remote-work-is-not-local-work-at-a-distance/#comments Thu, 25 Feb 2021 06:50:18 +0000 https://decoding.io/?p=1243

Jason Fried wrote a post about doing remote work, with the expectations of local employment. This post resonated with me very well since I had a couple of weird interviews lately. Just a side note: yes, I quit my current job as a Ruby backend developer at TerraCycle about three weeks ago, and I’ll start working as a frontend developer/product designer at Nearcut on March 10th.

There are still companies that refuse to accept that remote work is a viable alternative. They want you to be in the office because “this is what we did before the pandemic, and everything should be back to normal soon.” No, nothing will be like before, and companies should embrace that, not deny it.

Not everyone’s like that. Even big ones consider remote work a viable alternative but don’t have the hiring process and experience to work like that, so they’re relying on old habits.

The enlightened companies coming out of this pandemic will be the ones that figured out the right way to work remotely. They’ll have stopped trying to make remote look like local. They’ll have discovered that remote work means more autonomy, more trust, more uninterrupted stretches of time, smaller teams, more independent, concurrent work (and less dependent, sequenced work).

I’m interested in what COVID-19 will do to remote work because, seriously considering remote work is one of the positive changes of the pandemic that happened in many workplaces. People were forced to work from home. Many companies figured out how to do this successfully, and they don’t want to throw out this knowledge because “everything will be back to normal.”


Jason also writes about native platforms:

Porting things between platforms is common, especially when the new thing is truly brand new (or trying to gain traction). As the Mac gained steam in the late 80s and early 90s, and Windows 3 came out in 1990, a large numbers of Windows/PC developers began to port their software to the Mac. They didn’t write Mac software, they ported Windows software. And you could tell – it was pretty shit. It was nice to have at a time when the Mac wasn’t widely developed, but, it was clearly ported.

When something’s ported, it’s obvious. Obviously not right.

Stuff that’s ported lacks the native sensibilities of the receiving platform. It doesn’t celebrate the advantages, it only meets the lowest possible bar. Everyone knows it. Sometimes we’re simply glad to have it because it’s either that or nothing, but there’s rarely a ringing endorsement of something that’s so obviously moved from A to B without consideration for what makes B, B.

Maybe Basecamp should create a Catalyst version of HEY for Mac from their iOS app, which is quite nice, instead of having a cross-platform Electron thing on the desktop called a “native Mac app.”

]]>
https://decoding.io/2021/02/remote-work-is-not-local-work-at-a-distance/feed/ 1 1243
https://decoding.io/2021/02/1239/ https://decoding.io/2021/02/1239/#comments Tue, 23 Feb 2021 21:24:45 +0000 https://decoding.io/?p=1239

Nowadays, I use SVG icons everywhere, but preparing them is quite time-consuming. This website could help pick and use nicely designed ones for your next project.

]]>
https://decoding.io/2021/02/1239/feed/ 1 1239
zsoltbenke.me https://decoding.io/2020/11/zsoltbenke-me/ https://decoding.io/2020/11/zsoltbenke-me/#comments Wed, 04 Nov 2020 20:51:27 +0000 https://decoding.io/?p=1197

As I refresh my online presence with a new avatar and a new Dribbble profile, it occurred to me that I don’t have a page, which I can hand to somebody, and he/she can see what my work is all about. Not a portfolio, but a more superior page than a simple “About Me” in WordPress.

So I built one, featuring some of my work I did lately and, more importantly, the first web development tutorial I promised a couple of months ago. I plan to extract this article into a separate mini-site just for these tutorials, but for now, it nicely complements my “About” page.

Anyway, check out my work!

Screen Shot 2020 11 04 at 21 39 07

]]>
https://decoding.io/2020/11/zsoltbenke-me/feed/ 7 1197
I’m on Dribbble (again) https://decoding.io/2020/10/im-on-dribbble-again/ https://decoding.io/2020/10/im-on-dribbble-again/#respond Sat, 31 Oct 2020 13:22:31 +0000 https://decoding.io/?p=1183

It was a long time since I posted anything on Dribbble. In the last couple of years, I did so many exciting things, but I haven’t shown them anywhere; I did show some bits and blobs on Twitter.

Since I like to think about UI interactions and building tools, I should have a platform to show the results. And Dribbble is excellent for that. So from now on, I try to post new stuff I’m working on.

Checkout my Dribbble profile →

(Feedback is always welcome.)

Screen Shot 2020 10 31 at 14 11 37

]]>
https://decoding.io/2020/10/im-on-dribbble-again/feed/ 0 1183
How to Write a Git Commit Message https://decoding.io/2020/09/how-to-write-a-git-commit-message/ https://decoding.io/2020/09/how-to-write-a-git-commit-message/#respond Tue, 01 Sep 2020 09:27:14 +0000 https://decoding.io/?p=1064

Ha fejlesztő vagy, akkor az alábbi oldalt tedd el a bookmarkjaid közé és kezdd el alkalmazni az itt tanácsolt dolgokat:

If you haven’t given much thought to what makes a great Git commit message, it may be the case that you haven’t spent much time using git log and related tools. There is a vicious cycle here: because the commit history is unstructured and inconsistent, one doesn’t spend much time using or taking care of it. And because it doesn’t get used or taken care of, it remains unstructured and inconsistent.

Madarat tolláról, fejlesztőt git history-járól.

]]>
https://decoding.io/2020/09/how-to-write-a-git-commit-message/feed/ 0 1064
https://decoding.io/2020/01/702/ https://decoding.io/2020/01/702/#respond Thu, 02 Jan 2020 14:11:51 +0000 https://decoding.io/?p=702 Replying to: MuellerSimhofer:

As user I’ve seen Spotlight implementations for document based apps (usually just using NSUserActivity), but the problem is that they usually duplicate results. You get one result from Files and then another one from the app’s index.

Also keeping the index up-to-date seems hard, since I can delete files outside the app, but the app’s index still contains it: I have to start the app, to get the index updated. I’m curious how you guys can solve this for MindNode.

]]>
https://decoding.io/2020/01/702/feed/ 0 702
https://decoding.io/2020/01/700/ https://decoding.io/2020/01/700/#comments Thu, 02 Jan 2020 13:37:40 +0000 https://decoding.io/?p=700 Replying to: MuellerSimhofer:

I haven’t implemented anything like that for myself, but Spotlight indexes the Files app anyway. What’s the point of building a custom index?

]]>
https://decoding.io/2020/01/700/feed/ 3 700