Textile tiling workspace #
This document specifies how Textile’s main editing area is tiled, in the spirit of Obsidian and VS Code: a hierarchy of splits, draggable dividers, and one workspace region that consumes the chrome not reserved for navigation and status UI.
It pairs with PRODUCT.md: multi-pane layout supports living inside Textile, frictionless access, and keeps the editor performant by isolating resize and layout logic from document editing.
Layout model: binary split tree #
The workspace is represented as a full binary tree of splits:
- Each internal node is either:
- Horizontal (
row): children are tiled left and right. - Vertical (
column): children are tiled above and below.
- Horizontal (
- Each leaf is a tile (eventually a stack of tabs; today a single content surface such as EditPad).
Each split stores a ratio in (0, 1): the proportion of the parent’s axis allocated to its first child (the second receives 1 - ratio). Layout pixels are derived at render time from the workspace rectangle—so window resize preserves proportions automatically.
Splitting adjusts the tree:
- Split right (default): introduce or extend a horizontal split focused on the active tile.
- Split down: introduce or extend a vertical split.
When another tile is added “to the right,” the tree stays strictly binary by nesting new split nodes—same structural idea as VS Code, rather than widening a node beyond two children.
flowchart TD
shell [AppShell_future]
left [LeftNav_future_placeholder]
center [CentralColumn]
bottom [BottomBar_future_placeholder]
ws [Workspace]
shell --> left
shell --> center
shell --> bottom
center --> ws
ws --> rootSplit [Split_root_H_or_V]
rootSplit --> leafA [Tile_leaf_A]
rootSplit --> inner [Split_nested]
inner --> leafB [Tile_leaf_B]
inner --> leafC [Tile_leaf_C]
Rejected for v1: arbitrary grids, floating windows—not needed to mirror Obsidian/VS Code and harder to specify and resize correctly.
Terminology #
| Term | Meaning |
|---|---|
| Shell | The full Electron window: future left sidebar, optional top/right strips, optional bottom bar, and everything else. |
| Workspace | The remaining rectangle devoted to tiles—the split tree root is laid out here. |
| Tile | A leaf: one pane. Later: tab strip inside the pane; for now conceptual. |
| Split node | Internal node with exactly two children and one ratio along the split axis. |
| Active tile | The focused leaf; splits and tab actions attach here unless multi-select appears later (out of scope). |
Focus #
- Exactly one active tile in the workspace.
- Click (or equivalent) inside a tile moves focus when tiling UI exists.
Creating tiles #
- Split right: On the active leaf, replace it with a horizontal split: left = previous tile content, right = new empty tile.
- Split down: Same with a vertical split: top = previous, bottom = new.
- Initial ratio: fixed 50/50 (
0.5) for every new split (predictable behavior). - Target: Always the active tile unless a future multi-pane selection model is introduced.
Minimum tile sizes #
- Each tile defines minimum width and minimum height (pixels).
Policies
- While dragging a splitter, clamp the ratio so no tile violates minimums.
- If a requested split cannot satisfy minimums given the current workspace size, refuse the operation and give clear user feedback.
Resizing splitters #
- Only borders between two sibling children expose a draggable splitter.
- Dragging adjusts that parent node’s ratio; nothing else in the tree is reinterpreted besides layout pass.
- A vertical divider adjusts horizontal apportionment; a horizontal divider adjusts vertical apportionment.
Possible later enhancement: double-click a splitter reset to 50/50 (not committed for first implementation).
Window resize #
- On container size change (window or future chrome collapse), recompute geometry while preserving stored ratios.
- Subtract reserved areas for sidebar and bottom bar once they exist—workspace receives the leftover box only.
- No automatic equalization unless a deliberate reset layout command exists in the future.
Controls (conceptual—not all implemented yet) #
Users will manipulate tiles via:
- In-app UI (buttons, toolbar, pane headers).
- Menu bar (Electron).
- Keyboard shortcuts.
- Tab drag-and-drop (after tabs exist)—drag tabs, not tiles, to move editors between tiles or create splits according to drag targets.
Drag-and-drop is specified here but deferred until tabs exist.
Command identifiers (single source of truth) #
All surfaces should converge on stable workspace commands, for example:
| Command ID | Intended behavior |
|---|---|
workspace.splitActiveRight |
Split active tile to the right. |
workspace.splitActiveDown |
Split active tile downward. |
workspace.closeActiveTile |
Close / merge active pane (behavior TBD when one tile remains). |
workspace.focusNextTile |
Cycle focus between tiles (order TBD). |
workspace.setActiveTile |
Focus tile by id/path (called from clicks). |
Ratio updates during splitter drags belong to workspace.resizeDivider (or equivalently mutate split state keyed by splitter id)—exact API can follow concrete state shape.
These IDs should ultimately register in the broader actions system described under Related: command surface.
Near-term non-goals #
The following are explicitly postponed relative to earliest tiling milestones:
- Full file tree in the left sidebar (placeholder region only at first).
- Bottom bar content (word count, etc.).
- Top/right decorative or tool regions.
- Tabs UI and tab dragging.
They stay in shell/wireframe form until corresponding product phases land.
Implementation plan #
| Phase | Scope |
|---|---|
| 1 | Shell layout: reserve regions for future left nav and bottom bar (may be zero-width placeholders); workspace consumes the remainder. |
| 2 | Split tree + render: persist tree state; layout via flex or absolute geometry from ratios; leaves mount real content (EditPad) or placeholders. |
| 3 | Splitters: pointer drag on dividers; clamp to min tile sizes; cursors and hit targets. |
| 4 | Commands + keyboard shortcuts: wired to workspace command IDs; focus moves and close semantics. |
| 5 | Electron menu hooks: menus dispatch the same commands as keyboard and UI. |
| 6 | Tabs per tile plus drag-and-drop of tabs across tiles/split hints. |
| 7 | Persist layout to vault or global settings—format TBD. |
Related: command surface (shortcuts, menu, palette) #
Next architectural step (after tiling implementation begins):
Introduce a single catalog of actions/commands whose entries carry at least:
- Stable id (including
workspace.*above). - Human-readable title / description.
- Handler runnable from renderer (or delegated to main where needed).
- Optional shortcut metadata for registration with OS/Electron accelerator tables.
- Menu grouping for the application menu.
- Optional
enabledpredicate (when)
Electron menus, global shortcuts, contextual UI triggers, and a future command palette should all enumerate and invoke this catalog so labels, accelerator strings, and behavior live in one place. Tiling and editor features register side by side (e.g. “Toggle sidebar,” “Split right”) so the palette can search everything.
Details are intentionally out of scope for this tiling spec; document here only so product and roadmap stay aligned.
Decisions (record) #
| Decision | Choice |
|---|---|
| Tree shape | Strict binary splits only. |
| New split ratio | Fixed 0.5 initially. |
| Min size violation on drag | Clamp ratios. |
| Min size violation on split | Refuse with feedback. |
Changelog mindset #
Iterate this file when UX rules tighten (e.g. tab drag targets, persisted layout schema, or sidebar width behavior). Implementation PRs should reference updated sections rather than scattering layout rules elsewhere.