txt-layout-invalidation

Explains text layout invalidation models across TextKit 1 and TextKit 2 on Apple platforms.

2|Updated Sep 8, 2026
One-click install
npx skills add https://github.com/hanshi-notes/hanshi --skill txt-layout-invalidation-hanshi-notes
Or copy as Structured Prompt for Agent▼
Please help me install this Agent Skill.
Skill: txt-layout-invalidation
Source: https://github.com/hanshi-notes/hanshi/tree/main/.agents/skills/txt-layout-invalidation
Command: npx skills add https://github.com/hanshi-notes/hanshi --skill txt-layout-invalidation-hanshi-notes

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill includes references (resource) components.

What problem does it solve? Text edits, container resizes, and attribute changes can leave stale or missing layout in AppKit/UIKit text views, and it is hard to know which API actually triggers recomputation. This Skill provides the authoritative invalidation model for both TextKit 1 (NSLayoutManager) and TextKit 2 (NSTextLayoutManager) so you can reason about when layout is marked dirty, when it is recomputed lazily, and which manual invalidation call to use. ## Core Features & Use Cases - Dual-stack invalidation model: Documents what triggers glyph and layout invalidation in TextKit 1 versus element regeneration and fragment invalidation in TextKit 2, including the load-bearing performEditingTransaction wrapper. - Forcing and scoping layout: Covers ensureLayout variants scoped by container, character range, glyph range, and bounding rect, plus invalidateGlyphs, invalidateLayout, and invalidateDisplay. - Debugging sidecar: A references file with a stale-layout symptom decision tree, symbolic breakpoints, os_signpost profiling patterns, and viewport controller fragment-recycling guidance. - Use Case: After a programmatic text edit your TextKit 2 view shows stale fragments. The Skill explains that the mutation must run inside contentStorage.performEditingTransaction so elements regenerate and the viewport controller re-runs layout. ## Quick Start Ask the AI to explain why your TextKit 2 text view shows stale layout after a programmatic edit and which invalidation call fixes it.

Frequently Asked Questions about txt-layout-invalidation

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I force layout in TextKit 2 after a text edit?▼

Wrap the mutation in contentStorage.performEditingTransaction so elements regenerate and fragments invalidate automatically. To force recomputation manually, call textLayoutManager.ensureLayout(for:) on a limited range or trigger textViewportLayoutController.layoutViewport() for visible content.

What is the difference between invalidateGlyphs and invalidateLayout in NSLayoutManager?▼

invalidateGlyphs regenerates glyphs and is needed after attribute changes affecting glyph mapping such as fonts or ligatures. invalidateLayout keeps existing glyphs and re-lays them out, which suits container geometry changes the system did not catch.

Why does my TextKit 2 view not update after editing NSTextStorage directly?▼

Direct NSTextStorage mutations outside performEditingTransaction skip element regeneration, so layout fragments stay stale even though the storage changed. Wrap the edit in contentStorage.performEditingTransaction to keep the lifecycle consistent.

Do rendering attributes invalidate layout in TextKit 2?▼

No. Rendering attributes set via setRenderingAttributes are visual-only overlays and never trigger layout recomputation. Changes that affect line breaking or wrapping must go through storage attributes like font, paragraph style, or attachments.

When should I avoid ensureLayout for the full document range?▼

Full-document ensureLayout is O(document size) and defeats TextKit 2's viewport optimization. Limit the range to the viewport or the slice you need, or use ensureLayout(forBoundingRect:in:) over the visible rect on TextKit 1.

Why is my layout query wrong between beginEditing and endEditing?▼

Layout state is in flux during an editing batch, so measurements taken inside it see partial state. Move the query after endEditing() or defer it to the next runloop tick.