Skip to content #dev

This is not content

Posted May 2021 in #dev

Having yet to learn my lesson, I’m writing another CMS. My go-to excuse is that solving an old problem in a new language is a great way to learn. In truth, learning a new language is just an excuse to try again. When the unsolvable task invariably remains, at least I’ll have something to show for the effort.

It’s just such an interesting challenge. For example, the managed content should be consumable both in a browser and through an API. The input and output of the CMS should be static when possible, but dynamic when necessary. The system should be self-contained, open-source, easily self-hosted, optionally expertly hosted, accessible and extensible, inspirational yet invisible.

From a stakeholder perspective, the CMS should not expect editors to design, nor designers to code, nor developers to acquiesce to its choices. It should gently steer away from the common annoyances of the modern web. At the same time, the CMS should not attempt to be everything for everyone, but instead aim for a near-zero upfront cost to make it a safe default in the typical case.

From a development perspective, the CMS should not feel like a framework that dictates programming outside its purview, nor like a limited service that subs out one problem for another. Instead it should act like a fronting proxy that helpfully and orthogonally chips away at the ever recurring tasks of making a website.

Stale data beats fresh errors

Posted June 2018 in #dev

A web browser’s back button is more than a link to the previous page. It is the navigational equivalent of undo. Going back should restore the previous page, exactly as it was last time we were there. Conversely, the forward button is redo. These actions traverse history — they should never mutate state.

Luckily, the browser is a capable time machine on its own, at least for regular web sites. It will cache content, remember form values, reset scroll bars, and all the other things required to preserve the browser history. Just make sure to keep Cache-Control: no-store out of your response headers.

However, for web apps, where we make our own little world of routes and state, it’s up to us to tackle time travel. And while an implementation as polished as the browser’s may be out of scope, a little consideration goes a long way.

An expired session is not an error when going backwards. Navigating history should never clear data or result in a spinner. It should not show any sudden 4xx or 5xx errors because of a fetch. Previous form values should be restored, even if invalid. And navigating history should always work — even when offline.

Cherries picked

Posted May 2019 in #dev

  • Things already have names

    Naming things is the hard part, so don’t. Most things already have names. Use the API field, the design files, or ask the user. Projects fail without a common language.

  • Design systems make work

    Graphic tension beats branding if it the friction helps intuition. Design systems where native controls don’t fit only make more work. Users don’t mind the details.

  • The two world wide webs

    The divide between web apps and sites will remain. Apps should use more JS, while sites far less. Interactivity decides. But an unstated premise fuels discord.

  • Scale beats best practice

    Most opinions are wrong at some scale. Availability, security, and accessibility are all spectrums. Taste is acquired through experience in the scale bracket at hand.

  • Cherry-picked tidbits

    Ideas are cheap if you don’t have to think them through. Write out their abstracts in nice little boxes. Readers will helpfully fill in the blanks. People are nice like that.

Cascading consistency

Posted April 2018 in #dev

CSS is neither code nor markup. It’s not a query language, a configuration language, or a DSL. Instead, CSS is a style sheet language — a world of its own. Yet we haphazardly import rules from other fields. It’s far from given that our ideas about globals, inheritance, and modularity apply to the world of CSS.

The closest thing to a style sheet is not found in programming but in design. More than anything a style sheet resembles a style guide — a codified design specification. For what is a style guide but a set of design rules, invariants, and exceptions? And in both cases the ultimate goal is design consistency.

Good design is consistent. There are ground rules that apply by default, with extensions and exceptions as required. In other words, cascading inheritance is not a complexity brought by CSS, but an inherent challenge for any design language. Whether expressed in *.css or *.js, whether the cascade is implicit or explicit, inheritance is a fundamental property of good design.

This perspective informs one approach to writing CSS where we embrace cascading over isolation and inheritance over repetition. Instead of expecting consistent application of a written guide to isolated components, we use our limited pool of developer discipline for explicit reliance on global styles.

In this approach, any style that can be global should be global. Local styles should rely on styles defined further up the chain. Whenever possible, prefer unitless values and percentages to absolute measures. Prefer declarative centering to explicit offsets. Prefer intrinsics like currentColor to variables. Prefer em to rem and rem to px. Extend on a base without overrides.

Spend time on target

Posted February 2018 in #dev

It is usually more productive to talk about feature scope than about priorities. While slight shifts in priority are seldom actionable, scoping requires decisions. Scoping scales products by their number of features, and not by overall quality. Tradeoffs must be made and features must be cut — at every level of the stack.

The first step in scoping features is to get everyone on the same page. For this, words are often not enough. A shared understanding is better achieved through detailed sketches and interactive mockups. Too much code is written based on a false assumption of agreement. Make a feature obvious, then decide its scope.

The next step is to decide on which features to keep and which features to cut. Scoping is about tradeoffs. Each potential feature has an expected cost, utility, and interplay with other features. These tradeoffs should be difficult as they decide what the product will be. And iteration does not preclude planning.

When features are fleshed out and scoped in it is finally time to build. At this point there should be no fundamental questions unanswered or important thoughts unsaid. If there are split opinions it is time to disagree and commit. Any feature of uncertain scope should be pushed for re-planning in the next iteration. Plan more, code less, and remember where you’re going.