False positives interrupt your workflow. In RustRover 2026.1, we reduced them by up to 25% in real projects, so you’ll see fewer misleading warnings, more relevant suggestions, and smoother completion. Read on to learn what causes false positives and how we’ve been fixing them.
What are false positives?
The term false positive is used in many fields, including healthcare, finance, cybersecurity, and software development. A false positive occurs when a system incorrectly reports a problem that does not actually exist. In software development, this usually means that a tool reports an error, warning, or threat even though the code or system is functioning correctly.
What are false positives in RustRover?
In RustRover, a false positive happens when the IDE highlights something as an error even though the project compiles and runs successfully. For example, you may see red code in the editor while cargo build and cargo check report no issues. This can make it harder to trust IDE diagnostics and may interrupt your development flow.
Why does RustRover have false positives?
By default, RustRover highlights problems in two different ways:
After editing code, RustRover runs cargo check (or optionally cargo clippy) to detect compiler errors and warnings.
RustRover also has its own code analysis engine. This engine parses the code, performs name resolution and type inference, and provides editor features such as highlighting, completion, navigation, inspections, and quick-fixes.
Sometimes RustRover’s internal analysis engine behaves differently from the compiler. When its logic does not perfectly match the compiler’s behavior, false positives can appear.
Why does RustRover have its own code analysis engine?
If RustRover’s code analysis can produce false positives, why not rely entirely on cargo check? There are several important reasons.
IDE features require deep code understanding
RustRover needs code analysis for much more than error highlighting. Features such as code completion, Go to Definition, Find Usages, refactorings, quick-fixes, type hints, and macro expansion support all require a deep understanding of the code structure.
To provide these features, RustRover parses the source code into a syntax tree, resolves identifiers, and infers types. The Rust compiler can report errors and warnings, but it’s not designed to power interactive IDE features. That’s why IDEs such as RustRover and rust-analyzer require their own analysis engines. RustRover’s analysis engine is developed independently and is not based on rust-analyzer.
The IDE must work on broken code
The compiler often stops after encountering an error and may not continue analyzing unrelated parts of the project. IDE analysis must be more resilient. It needs to continue understanding the codebase even when some parts are incomplete or incorrect.
IDEs and compilers have different priorities
RustRover’s analysis needs to react instantly while you type. To achieve this, the IDE often analyzes only the affected parts of the project instead of rebuilding entire crates. This difference in priorities explains why IDE analysis and compiler behavior are sometimes not perfectly aligned.
How do we find false positives?
False positives are essentially bugs in the IDE’s analysis engine. We identify them in several ways:
User reports
Many false positives are reported by users in our issue tracker. This is extremely valuable because it helps us identify problems affecting real-world workflows. However, some reports can be difficult to reproduce, which makes debugging more challenging.
Anonymous usage statistics
RustRover can compare the output of our analysis engine with the output of cargo. This helps us measure how often false positives appear, although it does not provide enough information to reproduce the issues directly.
Running the Rust compiler’s test suite
We run the Rust compiler’s test suite against our own analysis engine. These tests are often small and isolated, making them useful for identifying specific problems. However, many compiler tests focus on edge cases that rarely appear in production code.
Testing open-source crates
One of the most effective approaches is running RustRover’s analysis on large collections of open-source crates and comparing the results with cargo. This allows us to:
Reproduce real issues.
Estimate the impact of each bug.
Test popular ecosystems.
Verify that fixes do not introduce regressions.
Fixing false positives in RustRover
Reducing false positives is one of the most common requests we receive from users. After the release of RustRover 2025.2, we assembled a dedicated task force focused specifically on identifying and fixing false positives. We concentrated on two main sources: user-reported issues and large-scale open-source crate analysis.
Inspired by the Rust compiler’s crater project, we built an internal system that runs RustRover’s analysis against thousands of open-source crates and reports mismatches between IDE diagnostics and compiler output. This system helps us identify high-impact problems much faster and continuously improve the accuracy of RustRover’s code insight.
Our goal is simple – make IDE diagnostics more trustworthy so developers can stay focused on writing Rust code.
How you can help
While we’ve made good progress, there is still a long way to go, and our work on reducing false positives will continue in future releases. If you notice any false positives, please report them in our issue tracker so we can continue improving RustRover’s code insight.
On June 1, GitHub Copilot’s usage-based billing became active for all Copilot plans, and developers reacted quickly and loudly. A Pro plan still costs $10, but it now comes with a monthly pool of AI credits. Those credits are priced at a penny each, and they’re consumed according to the model used and the tokens processed, including input, output, and cached tokens. For a heavy agentic session running a frontier model, that makes spend feel very different from a flat subscription.
That’s the news, and it’s worth understanding, but it isn’t the important part. Nothing about the underlying cost of agentic work actually changed on June 1. The tokens were always being consumed, the loops were always running, and the tool calls were always expanding the context. What changed is that the meter became visible. A workload that had been quietly subsidized under a flat rate started showing up as an itemized bill.
Where the tokens go
To see why the bill landed so hard, it helps to compare two things that look similar and bill very differently. A chat completion is close to a single transaction. You send a prompt, the model sends an answer, and you pay roughly once for the input and once for the output. A tool-using agent doesn’t work that way at all. An agent doesn’t answer a question so much as work toward it, and it works by looping. It reasons about the task, calls a tool, reads the result, reasons again, calls another tool, and continues until it decides it’s finished.
Every pass through that loop carries a cost that’s easy to miss. In many agent harnesses, each turn carries forward a large share of the accumulated context: prior messages, tool descriptions, retrieved files, and tool results. Even when some of that context is cached, summarized, or pruned, the system is still doing metered work to preserve enough state for the next decision. The final answer you actually wanted is only a thin slice of what you paid for. The loop is the bill.
This is why agent cost doesn’t scale politely. It scales with the number of turns, and the number of turns scales with how much discovery the agent has to do, which in turn scales with how vague the request was and how much irrelevant context it’s dragging along. A clean, well-scoped task might finish in three turns, while the same task posed as an open-ended question might wander through 15, each carrying the cost of everything that came before it. Under a flat rate, that difference was invisible. Under usage-based billing, it’s the difference between a small interaction and an expensive one.
Tool design is now part of the cost model
I wrote recently about a hidden tax on Model Context Protocol servers: the way an overstuffed tool catalog quietly degrades a model’s ability to route to the right tool. Bloated descriptions, overlapping responsibilities, and vague parameters make the model’s job harder and its choices worse. That argument was about accuracy. The billing change adds a second invoice for the same bloat, and this one is denominated in dollars.
The tool catalog is often part of what gets carried through the agent’s loop. A tool described in three tight sentences and a tool described in three rambling paragraphs may both function, but the second one pays rent in the context window every time an agent has it loaded. Multiply that across a catalog of 40 tools and a workflow that runs a dozen turns, and the cost of verbose tool design stops being a rounding error. Tool design was already a correctness discipline. It’s now a cost discipline as well. The same audit that tightens routing accuracy tightens the bill.
Where prompt discipline runs out
There’s a layer of this that individual users can control, and it’s worth knowing because the savings are real and immediate. Two patterns matter most, and I’ve been handing both to the engineers on a pilot I run for a large healthcare organization. They aren’t magic tricks. They’re ways to keep the agent out of unnecessary discovery loops.
The first pattern is about input. Prompt the agent like a short requirement rather than a broad question. A request such as “look at the encounter data and tell me what you find” forces the agent into discovery mode, where it burns turns figuring out what you meant, and every one of those turns carries the full context forward. Compare that to a prompt that front-loads the specifics by naming the project and the table, naming the date field to filter on, stating the output shape you want, and calling out anything that should be excluded. A better prompt would be: “Using the curated clinical project and the silver-zone encounters table, show total encounters by month for calendar year 2025, use admission_date_time for inclusion, and return one row per month ordered chronologically.” The second prompt collapses the loop. The agent has what it needs on the first turn, so it does the work instead of interviewing you for it.
In practice, the difference isn’t just polish. The vague version forces the agent to discover the data model, infer the date semantics, choose an aggregation, and decide on a display format. The specific version turns the task into a bounded query. That difference shows up in accuracy, latency, and cost.
The second pattern is about output, and it’s the lever most people overlook. Ask for plain text or Markdown during the intermediate steps, and save rich HTML formatting for the final, confirmed deliverable. Formatted output is expensive to generate, and requirements shift. If you ask for a polished HTML report on the first pass and then change a filter, you pay full output-token freight to regenerate all that layout, often more than once. The cheaper habit is to validate the numbers in text and format only at the end.
These patterns work, and they also have a ceiling. Both of them put the entire burden of cost control on the user, and they hold only as long as every user exercises the discipline on every prompt. The day someone reverts to “tell me what you find,” the savings evaporate, and the only thing standing between the team and a surprise invoice is a budget cap that reports the overspend after it has already happened.
Cost is a governance problem, not a budgeting one
That fragility is the real lesson. A budget cap is a backstop rather than a control. It will stop a runaway, but it tells you that you overspent rather than why, and it does nothing to make the next run cheaper. Treating cost as a budgeting problem leaves you forever reacting to the meter, while treating it as an architecture problem lets you build the savings in once and stop relying on everyone’s good behavior.
That means the controls that matter belong on the platform rather than in individual prompts. By the platform I don’t mean the agent itself, the coding assistant or chat client a developer drives day-to-day, and I don’t mean the model or a router sitting beneath it. I mean the control plane that sits above the agents, the layer where an organization enforces policy, access, observability, and now cost across every agent and model its developers touch. An administrative console that gives IT visibility into who is doing what and which capabilities they can install is an early, narrow instance of it. A router that sends planning to a cheap model is one feature that belongs there. The platform is where the rules live, and the agent is a consumer of those rules rather than the place you set them. The platform should route models by task, using cheaper models for planning and reserving frontier models for work that earns the price. It should bound the loop, requiring the agent to check in after a fixed number of iterations. It should cap tool-result payloads so a careless query cannot dump a million rows into the context window. It should default intermediate work to plain text, making the cheap path the path of least resistance instead of something users have to remember.
Every one of those controls is something a user can approximate by hand and something the platform can simply guarantee. This is the same principle I keep returning to in the context of data access, where safe behavior cannot depend on the person at the keyboard remembering the rules. Prompts guide behavior. Guardrails make the cheaper and safer behavior the default. Cost governance is guardrails as control plane, with a dollar sign attached, enforced at the same layer where you already enforce who is allowed to see which row.
The pattern, not the vendor
It would be a mistake to read this as only a GitHub story. GitHub is the current example because its change is visible and recent, but usage-based billing for agentic work is the direction of travel for many AI tools. The economics under the hood are similar: Agentic workloads turn single answers into loops of model calls, tool calls, and context management. The flat-rate subsidy was always going to come under pressure once the workload shifted from autocomplete to autonomy.
The organizations that treat June 1 as a pricing event will optimize a few prompts, grumble, and move on until the next vendor changes its meter. The ones that treat it as an architecture signal will push the cost controls down into the platform, where they hold regardless of which provider is counting which token. That’s the more durable place to stand. The bill didn’t get bigger this month. It got honest, and an honest bill is the kind you can engineer against.
BONUS: Why a Former Chess Champion Thinks Your Leadership Is Stuck in the Opening Game
John Whitt spent 30 years managing billion-dollar construction portfolios in corporate America — sleeping five or six nights a week in hotel beds, traveling the country, winning at someone else's game. Then he walked away. In this episode, he breaks down what chess taught him about business phases, why generosity outperforms hustle in the long run, and how the "pause factor" keeps leaders from burning out while scaling their impact.
From Corporate Construction to Coaching — The Move That Changed Everything
"I spent 5, sometimes 6 nights a week, sleeping in a hotel bed, traveling around the country, and it really wasn't good for my sanity, it wasn't good for my family. And then the company decided to move from Southern California to Dallas, and so that was like the — I'm not going to Dallas move, and it's time to start something else."
John's corporate career was successful by every external measure — managing $500 million construction portfolios at companies like CB Richard Ellis. But the lifestyle was hollowing him out. He'd been thinking about leaving for a while when the relocation to Dallas forced his hand. Through behavioral assessment work, he discovered coaching was where his strengths naturally pointed — it had been his primary leadership style all along. In 2010, he invested in a Focal Point coaching franchise, which gave him the tools and training without having to reinvent the wheel. Combined with 30 years of corporate relationships, it was enough to launch. His reflection on the transition is simple: "The cool thing about coaching is that we're just helping people."
The Chess Game of Business — Opening, Middle, and End
"The way the chess game is played at the higher levels has influenced my way of thinking essentially for the rest of my life. The opening is where you're getting started — startup business, takes a lot of hustle, a lot of energy. But then the transition happens to the middle game, where you have to think a lot more strategically, and tactically with the right move in the right order, because the wrong order will not get you the results you're looking for."
John played in the United States Chess Championships in 1976, and the framework stuck. He maps business growth to three chess phases: the opening (startup hustle, high energy, you do everything), the middle game (strategic delegation, building systems, hiring people with an ownership mindset), and the end game (transitioning assets and resources to serve the life you actually want). The danger zone is the opening-to-middle transition. Founders and leaders get trapped being the go-to person for everything — solving everyone else's problems during business hours and doing their own work after hours and on weekends. The middle game demands a different skill: learning to operate on the business instead of always in it. And it can't happen overnight — you have to prioritize what to change, in what order, or it gets jumbled up.
Accomplishing Goals Through Others — The Magic of Discretionary Effort
"The magic is accomplishing goals through other people, because when you do that, you're going to do big things. As an individual, you can only do so much. There's only so many hours in a day."
John keeps coming back to one idea: if you're doing it all yourself, your impact is capped at 24 hours. The real unlock is getting other people to give their discretionary effort — that extra gear where someone stays 20 minutes longer because they care, or thinks about the project at home because they're genuinely excited. Discretionary effort isn't something you can demand. It comes from inspiration. John frames it through WIIFM — "What's In It For Me?" — everybody's favorite radio station. Leaders who skip that question get compliance. Leaders who answer it get mountains moved.
The flip side is equally important: many leaders have never been on a high-performing team, so they don't know what they're missing. They accept compliance as normal. Others are smart and capable but lack the relationship skills to inspire. John's point is clear: leadership through inspiration is a learnable skill, not an innate trait.
Generosity as Strategy — Time, Talent, and Treasure
"Generosity always — I mean, this is unequivocal — always gives you better long-term results. If you plan to be generous, if you say this is who I am and I will do the work that's necessary to be generous, then you will always get better long-term results."
John's 4-Facet LifeShine Generosity Process puts generosity at the center of leadership — an unusual move in a world that defaults to performance metrics and execution frameworks. His argument is that generosity isn't soft. It's strategic. The framework starts with unique identity (who are you?), then moves through three dimensions: time, talent, and treasure. Most people think generosity means writing a check. John says time and talent are far more powerful. A leader who invests the time to communicate vision and inspire the team is being generous — and that generosity compounds into better team performance, stronger relationships, and less burnout over time.
The risk, though, is over-giving. Agile coaches and scrum masters who tie their identity to the work are especially vulnerable — they give so generously at work that they burn out when results don't match expectations. That's where the plan matters: define the life you want, build the business or career to serve that life, and stay disciplined about boundaries.
The Pause Factor — How Leaders Protect Their Thinking
"You gotta learn to say pause. That's a great idea, I understand what you're saying, we need to spend a little more time on that — so let me schedule some time later. Because right now, if I spend all that time, it's not going to get my best thinking, it's not going to get my best response."
People bring problems to leaders constantly — personal problems, business problems, urgent and not-urgent mixed together. The instinct is to solve immediately. John teaches leaders the "pause factor": acknowledge the importance of what someone brings you, then schedule dedicated time to address it properly. This isn't avoidance — it's quality control for your own thinking. When you're distracted and rushed, you give worse answers. When you pause, you also create space to ask: is this mine to solve, or does it belong to someone on my team?
John extends this to how teams bring problems: train people to come with clarity — here's the problem, here's the challenge, here's some potential solutions. That way the leader can triage effectively in a short time instead of getting pulled into an unstructured conversation that eats an hour.
About John Whitt
John Whitt is a leadership strategist with 30+ years of business transformation experience, from managing $500 million construction portfolios at companies like CB Richard Ellis to coaching small business owners. He's the author of Checkmate!: Winning Tactics for Translating Ideas Into Money and creator of the Whole Life Leadership experience.
Join Stephen Rice and Arvind Mishra for a fun look at what's new with OneDrive on Mac! In this episode of Sync Up, they chat with Jack Nichols, Partner Software Architect for OneDrive, about how the team is making OneDrive feel more native, reliable, and polished for Mac users.
From a refreshed Activity Center and updated settings experience, to the new Native Sync Engine and future support for syncing up to 1 million files, this episode is all about making OneDrive on Mac faster, smoother, and easier to trust in the background. You'll also hear how OneDrive is improving performance, battery life, Spotlight search, and overall sync reliability while continuing to listen closely to feedback from the Mac community.
Apple used the WWDC26 keynote to introduce a new generation of Apple Intelligence that expands on last year's generative features. The upgraded system runs on a privacy‑preserving architecture that uses on‑device models and Private Cloud Compute. The new Siri AI is deeply integrated across iPhone, iPad, Mac, Apple Watch and Vision Pro. Siri can now understand what's on screen and draw on personal context to search across messages, photos, emails and third‑party apps. When needed, it reaches out to the web to provide up‑to‑date answers. A dedicated Siri app lets users revisit past conversations and syncs history across devices via iCloud.
Apple Intelligence also powers new experiences across the system. Spatial Reframing in Photos lets users re‑compose pictures after they are taken. Image Playground can generate high‑quality images, and Messages and Mail offer AI‑driven suggestions for replies and reminders. The intelligence features support multiple languages, run on devices with A17 Pro or M‑series chips and will be available in the fall with iOS 27.
Foundation Models and Core AI Frameworks
Foundation Models framework
For developers, the most transformative announcement is the Foundation Models framework in iOS 27. It is a native Swift API that gives apps direct access to the same on‑device language model powering Apple Intelligence. Developers can switch between Apple's model, cloud models (such as Claude or Google Gemini) or any provider that implements the new Language Model protocol. The framework supports multimodal prompts, allowing apps to pass images alongside text and use Vision tools like OCR and barcode readers. Dynamic Profiles let developers swap models, tools and instructions during a session so their app's behavior adapts to the user's needs.
Smaller teams benefit from cost‑free access: developers enrolled in the App Store Small Business Program and with fewer than 2 million first‑time downloads can use Apple's next‑generation models on Private Cloud Compute at no cloud API cost. A new Evaluations framework helps verify that AI features behave correctly across dynamic conditions.
Core AI
Where Foundation Models provides access to generative models, the new Core AI framework lets developers bring their own models on‑device. Core AI offers a memory‑safe Swift API and accompanying Python tools to load, specialize and run models entirely on Apple hardware. It delivers zero server dependencies and zero token costs while supporting everything from compact vision models on iPhone to multi‑billion‑parameter language models on Mac. Core AI integrates with ahead‑of‑time compilation, a visual tensor debugger and optimization tools to convert PyTorch models for the Core AI runtime. Apple noted that Core AI is optimized for Apple Silicon and also powers Apple Intelligence experiences across the system.
App Intents and System‑wide Intelligence
The App Intents framework has been expanded so Siri and Apple Intelligence can perform more actions with third‑party apps. Entity schemas allow an app's content to be indexed in the Spotlight semantic index, making it discoverable by Siri. Intent schemas define actions that users can take naturally without pre‑defined phrases. A new View Annotations API maps views to entities so users can reference on‑screen content conversationally. Apple also introduced an App Intents Testing framework to validate integrations without UI automation.
The Platforms State of the Union elaborated on how App Intents works with the System Orchestrator, Spotlight semantic index and the App Toolbox to route user requests across apps. Siri AI can now surface your app's content and actions through widgets, the Action button and voice interactions, and these integrations automatically improve as Siri learns new languages or dialects.
Liquid Glass Design and Platform Improvements
Liquid Glass refinements
Last year's Liquid Glass design language unified Apple's platforms. For 2026, Apple tuned it to improve readability and depth. Liquid Glass now diffuses complex content more effectively and adds a darkened edge with brighter specular highlights. Users can personalize the effect with a slider ranging from ultra‑clear to fully tinted. Apps already using Liquid Glass get these improvements automatically without recompiling. The design adapts to accessibility settings (e.g., reduced transparency and increased contrast) and introduces colored sidebars, refined toolbar behavior and improved icon rendering with refraction effects.
Resizability and SwiftUI
With iOS apps appearing on iPad and Mac via iPhone Mirroring, Apple introduced resizable iOS apps. Rebuilding with the latest SDK automatically opts an app into resizability. SwiftUI and auto layout provide basic support, while the new Resizable iOS Simulator and Previews let developers test across sizes. A built‑in agentic coding skill helps find and fix resizability issues.
SwiftUI itself gains richer interactions such as reorderable containers, allowing drag‑to‑reorder lists and grids with minimal code. SwiftUI's improvements target speed, dynamic interactions and new capabilities, making it easier for developers and encouraging migration from cross‑platform technologies.
Xcode 27: Enhanced Productivity with Agentic Coding
Apple’s flagship IDE saw major updates. The new Xcode 27 focuses on two pillars: intelligence and daily usability. Performance improvements make projects load faster, debug sessions more reliable and the console capable of handling heavy logging. Xcode 27 is 30 % smaller, now runs only on Apple Silicon and downloads agents and documentation in the background to stay up‑to‑date.
Personalization options now sync settings via iCloud. When setting up a new Mac, Xcode imports preferences and Git configuration automatically. Creating a new project jumps straight into the editor, deferring file names and bundle IDs until later. Developers can customize the toolbar and choose from new color themes such as Emerald, Neon Noir and Coral Reef. Xcode Cloud builds are up to twice as fast and require no App Store Connect setup, supporting Apple Vision Pro and Metal apps.
A Device Hub merges the simulator and physical device management into one interface. It supports pinch‑to‑zoom, two‑finger scrolling, dynamic resizing and testing of various system settings like dark mode and text size.
Agentic Coding
Xcode 27 weaves coding agents into every layer. Agents can plan, implement and test features. Developers start by describing their idea; the agent generates a plan, asks clarifying questions and produces code and UI previews. Agents can run tests in the simulator, localize strings, and even identify and fix crashes based on crash logs. Xcode 27 includes specialized skills—for SwiftUI, accessibility and performance—that help agents produce high‑quality code.
Other Notable Frameworks and Tools
Platform improvements: The new SDK makes apps launch faster and adds enhancements across frameworks, media, search and accessibility.
Game Porting Toolkit 4: Features agentic coding skills for Metal and game development, speeding up ports to Apple platforms.
Music Understanding framework: Enables six‑dimensional audio analysis and works with NowPlaying to integrate playback with Lock Screen, Control Center, Dynamic Island and CarPlay.
Photo and camera APIs: Core Image RAW processing APIs (version 9) provide improved sharpness and color, and new APIs support high‑resolution photo capture and Center Stage front‑camera features
An ersatz CSS tutorial for people who need to style a web page, but aren’t web developers. I am a
wrong person to write this kind of thing, as I have neither the time, nor experience. I’d much
rather read a book about this. Alas, I had to learn all this stuff from trawling MDN, so perhaps
it is valuable to document what I have so far.
CSS, HTML and Web APIs are truly vast, and it takes a career to become a professional. The good news
is that modern web has a reasonably-sized, learnable subset which is enough for simple tasks like a
programming blog or a simple GUI. I haven’t seen a resource that teaches just this subset, but
it’s not too hard to figure this out. The bad news is that there’s also a nasty set of gotchas,
which will mess up your page, which you won’t suspect to exist, and which will need days of
debugging to figure out. Still, it’s not that bad. I am quite happy with the styling on this site,
and it’s only about 200 of readable CSS.
Good: HTML5 semantic tag names
It’s worth looking through MDN
Elements Reference. There
aren’t that many elements, and things like main, article, nav, kbd make it much easier
to structure your page. Less obvious:
ul for any kind of list, like site’s sections in header > nav.
details for table-of-contents (check the source of MDN).
dl/dt for list of pairs.
Bad: Wrappers
If you “View Source” on any “real” website, you’ll notice that everything has
layers and layers of wrapper elements, so you might be tricked into thinking that wrappers are how
you solve layout problems. I can’t really agree or disagree here, as I never wrote “production” CSS,
but, in my experience, it’s much easier to understand if you do the opposite — restrict yourself
to using only markup-meaningful semantic tags, and then figure out CSS which works with the markup
you have.
Bad: Layout
This one is not an exclusively Web problem, layout is a struggle in every GUI framework I know.
Imagine a fixed sized raster image, and a paragraph of text describing it. There are many ways to
arrange these two elements on the screen’s rectangle. Generally, for every given width and height,
you can do a decent job, as long as the total area is enough. A typical GUI is a hierarchy of such
boxes, with a lot of “layout freedom”. The problem though is that layout of each box affects the
layouts of all other boxes, as you generally want all boxes to meet exactly, without gaps and
overlaps. An important negative realization is that the layout algorithm doesn’t exist. There
isn’t a fully general solution to positioning and sizing GUI boxes. Rather, different systems use
different sets of heuristics to do the job, from simple
RectCut, to
fully general constraint solvers, with
everything in between.
It is hard to get the mental model of how layout works, in general. So, don’t think “how
can I do my layout in a given system”, think instead “what possible layouts are allowed by the
system”.
Bad: Browser defaults
Let’s start with a bare (but still semantic) HTML markup of a blog
article, without any CSS. If you open it in a browser, it will show something. The content isn’t
unstyled — the text is of a certain color, font and size. Headers are bigger than the main text,
links are underlined, etc. These are the default styles of your browser. They are helpful! The
problem is that these styles differ between the browsers. So, even when you add your own CSS, and
the end result looks fine in your browser, I might see something different, because you might rely
on a browser default, without knowing it. The last bit is the killer here — the problem is in
something you didn’t write.
The general solution here is a CSS reset,
or normalization — starting your CSS with an explicit set of rules, overriding defaults. Not because defaults are inherently bad, because they are inconsistent. I don’t know which set of rules you need to override in practice, it’s a good idea to
compare several existing CSS resets.
This touches on the big question: should you style your web page? There are two competing views of
the Web platform — some people treat it as a flexible, adaptive, primarily visual medium for
expressing design, others would prefer if the Web focused on delivering the content, allowing each
user to customize the presentation. My personal answer here is pragmatic — by default, an unstyled
page is poorly usable and looks bad. I would have preferred the world where CSS-less pages were
readable as is, but, in this world, I think it is helpful to style the content. At the same time,
it’s a good idea to allow advanced users to bring their own CSS. Make sure that your HTML markup is
reasonable, that you don’t overfit your HTML to CSS (vice-versa is fine), and that your page
functions in reader mode.
Good:Classless CSS
You can’t reset styles to true neutral nothing: if
you make the text invisible (white or transparent), it is still a style. So you might as well
embrace it: after reset, style common HTML elements directly. For example, to set your favorite font
for all code snippets:
If you use main, header, footer, nav tags you can set the overall page layout without
writing any CSS selectors. This of course requires making assumptions, in CSS, about the structure
of your HTML, but, like, this is your HTML and your CSS, you can do whatever, and, if you don’t like
the result, you can always change it!
Bad: CSS selectors
In programming, we collectively came around to distrust inheritance and prefer composition. Default
CSS is like supercharged inheritance, each design element on your web page is affected by multiple
rules, and you can always “monkey patch” existing elements by appending to your CSS. There’s an
unfortunate gap between CSS affordances, and what you actually want to do. The two reasonable
approaches are:
Conclude that CSS selectors add abstraction capability along the wrong axis, and stick to
classless CSS and inline styles, using something like Tailwind to make writing inlines prettier, and
something like JSX (or any other templating engine supporting composition) to avoid repetition in
HTML.
Use CSS nesting to avoid
writing “far reaching” selectors and style component-per-component:
header { /* Site Header */margin-bottom: 2rem; & nav {/* Styles, specific to nav in the Header. */ }}
Bad:box-sizing
UIs are recursive rectangles, layout is the process of figuring out where each rectangles
goes, and it is determined by the sizes of rectangles themselves. So, understanding what is the
size is quite fundamental. Sadly, by default the definition of size in HTML is very unintuitive:
element’s width and height do not include element’s border and padding, which leads to surprising
results: everything looks perfect at first, but increasing padding somewhere shifts the entire
layout unexpectedly. For this reason,
* { box-sizing: border-box; }
deserves to be the first line in your CSS reset. It makes elements encapsulated, such that adding
borders is a local-only change.
Chaotic Good:margin collapsing
Suppose you want to have a 8px gap around an element. You would think that you need to set the
padding property. But that would be wrong — if you have two such elements next to each other,
the gap between them would be 16px. The paddings would add, creating a visual gap larger than
intended. You want something more akin to social distancing, where if one person is more
introverted, this person’s bigger radius of exclusion is what defines the distance. And that’s how
the margin property works. Two neighboring margins are combined using max rather than sum.
Margin collapsing is very useful, but it can surprise you. E.g. I think child margin can
stick beyond parent’s? To be honest, I don’t have a good intuitive understanding of margins, but I
know enough to at least identify when it is the problem.
Margins are also one of the indirect inspirations for this post. In
Julia Evans writes that you generally don’t want to set margin on an element, and should rather let
the parent control the inter-element margin of the children, using the so-called owl selector:
section > *+* {margin-top: 1rem;}
That is, add margin to all section’s children exempting the first one. I didn’t know that! And,
given all the pain that margin gave me so far, I actually get why you want to do this, and why this
is a good idea. But it bugs me that you can’t learn that without becoming “professional” web
developer, or reverse-engineering someone else’s CSS framework.
Bad:Default (flow)
layout
Layout in general is tricky, because there’s no universal “layout
algorithm”, just a bunch of special cases. But what does HTML actually do? The default layout
algorithm I think goes back to the origin of HTML as a language for documents, and overfits a
use-case of producing papers — mostly text content with some illustrations, where the text can
flow around the pictures. That’s actually what you want for the main body of text of your blog, but,
as soon as you want to actually control the spatial arrangement of the elements on your page, you
want something different, for example…
Good:flexbox
This is really what separates modern web-development from the olden days, where you’d need a CSS PhD or
a full-blown opaque CSS framework to be able to say “this goes to the left, and this goes to the
right”. This layout allows you to arrange a series of elements either vertically or horizontally,
adapting to the available space. It is rather complex and I can’t use flexbox without referencing
MDN all the time, but usually I am able to get things done in the end.
Bad: responsive design
Modern CSS allows querying screen size, and implementing conditional logic based on that — a
design that “responds” to user-agent constraints. This probably what you should use for “real” CSS,
but note that HTML is inherently responsive. Unlike PostScript (PDF), it will automatically reflow
the paragraphs when you change window size. So, it’s a good idea to avoid writing explicit
responsive rules, and just rely on layout to do the reasonable thing. For example, this blog looks
OK on mobile, tablet and desktop without any explicit @media queries. Unconditionally setting max-width on the main column of text is all that it takes.
Lawful Evil: pixels 1px does what you want, but not what it says. It’s not a size of one physical
pixel on your screen. Rather, it’s a measure of visual
angle. That is, 1px should look perceptually the same on
any screen, and it is converted to different number of physical pixels, depending on the screen size, its pixel density, and the typical viewing distance. So you can just size everything in pixels, without thinking about different
displays’ pixel densities. It gets weirder. CSS allows “real” units like centimeters or inches, but
they are also angles, because everything is defined in terms of pixels.
Doubleplusungood:font-size
Flexbox is a good way to layout UI-elements. Flow layout works ok for laying out paragraphs of text.
But what happens on the level of individual lines and glyphs is, in my opinion, a train wreck and a
noob trap. Let’s start with the basics: if you write
font-size: 16px
then 16px is the size of what? Sadly, the answer is “nothing in particular” — this is a size of
a virtual box around the glyph, but the box isn’t tight, and the size of the glyph varies, depending
on the font. Luckily, font-size-adjust property can fix it, and make font-size consistent across
fonts. See these two posts for details:
Though, at the moment font-size-adjust seems to be very niche, so, while personally I’d put
font-size-adjust: ex-height 0.53;
right next to box-sizing, few pages do that.
The next issue with font-size is a thorny question of defaults. The good news is that it’s one of
the properties that is fairly consistent across browsers, with 16px being the overwhelming
default. The bad news is that, depending on the font, 16px can be on the smaller size. Not
completely illegible, but very close to the lower bound. What’s worse, some default fonts are
particularly small. For example, on Apple,
font-family: serif
looks much smaller than sans-serif, and is almost uncomfortable to read at 16px.
Can you just set
font-size: 18px
or whatever works best for your chosen font? I think the answer is yes, but there are some caveats to
keep in mind. Refer to
Accessibility: px or rem?
for details. The issue is that modern browsers support two ways of making text on a page bigger:
Zoom, which has a dedicated UI element, shortcuts/gestures, per-page persistence/overrides
and a global default.
Changing default font-size, a global setting buried deeply in the configuration page.
Setting font-size in your CSS disables that second approach.
Taking everything together: don’t assume that text on your page will be readable by default, check
different configurations. Set font-size-adjust to reduce the number of degrees of freedom and to
pin down the meaning of font-size. If the result looks fine with your chosen (or your user’s
default) font and default font-size of 16px, then you are done. Otherwise, set font-size to a
bigger number. Afterwards, check that the page is readable in reader mode as well.
Bad:line-height
Despite the name, line-height doesn’t set the height of a line. It is a height of a run of glyphs,
set in the same font. The two coincide when all the text is in the same font. But if you have,
e.g., some words set in monospace font, you are in for a surprise. While font-size-adjust fixes
the size of a glyph inside the box, it still leaves its relative position unspecified. So, when two
runs of text in different fonts are aligned vertically to share the baseline, their line-height
line-boxes get shifted relative to each other: one sticks below, one sticks above. The line height
overall becomes larger that what you’d expect, as it is configured as a union. See
Bad: vertical rhythm
If you google long enough this cluster of problems, sooner or later you’ll come across the idea of
vertical rhythm, that you should make sure that lines are in the same relative position across
different paragraphs, even if you have headings, images, and what not. As if there’s invisible
lined paper behind your web-page. As far as I can tell, this is pure voodoo and is not useful. If
you do two-column layout, then you want lines on opposite sides to align, but it makes no sense to
jump through hoops for a single-column layout (hat tip to @chrismorgan).
Bad:word-break
The genius of the flow layout is its dynamism. It takes a moment of reflection to appreciate the
technical marvel of text breaking itself neatly into lines as the window is resized to be narrower.
Getting that to work for the first time ever in the world of durably printed text must have felt
incredible. But the magic has its limits — you can only break the line at the whitespace, or at
the hyphenation points. And some long spans, like inline code or URLs, might be unbreakable. This
leads to overflow annoyance on mobile devices, something you notice only after you publish your
work. There’s no one trick to fix it, but some tips are available here:
Against Horizontal Scroll
for details.
And … that’s all I remember so far? I reiterate my request for someone to write a short 100-page
book explaining just enough of HTML&CSS to make a simple blog without getting collapsed by the
margins!