# New-file naming dialog & editor filename header Date: 2026-07-30 ## Problem Creating a note in glint leaves the buffer unnamed. The name is asked for later, at the first Ctrl+S, through a one-line prompt in the bottom status bar (`ModeSaveAs`). Two consequences: - The name is an afterthought. A new note has no identity until it is saved, and the Harper language server sees it under an untitled URI until then. - Editor mode never shows the filename anywhere. Preview mode does — it renders a full-width title bar above the body — so the two modes disagree about whether the document has a visible name. ## Goals 1. Ask for the filename up front, in a centered dialog, whenever a new file is made. 2. Show the filename in editor mode as an uneditable header bar, styled like the title bar preview already renders. 3. Collapse the two naming UIs (save-as, new-file) into one component, and reuse it for renaming an existing file. ## Design ### Approach A new `internal/dialog` package holds the centered prompt as a self-contained Bubbletea sub-model: prompt title, target-directory hint, text input, validation, and Enter/Esc handling. `App` owns a `*dialog.Model` and a new `ModeNamePrompt`. `legibleText` and `hexToRGB` move from `internal/preview` into `internal/theme`, so the editor header and the preview title bar derive their colors from one place and cannot drift apart. Alternatives considered and rejected: - **Everything inline in `app.go`.** Fewest files, but `app.go` is already the largest file in the repo (1182 lines) and the dialog would only be testable by driving the whole app. The color logic would also be duplicated against preview's. - **Header owned by the `editor` package.** Rejected: the header is document chrome, not buffer text. `editor` already carries wrap, scroll, and undercurl geometry, and the header's text comes from `App.path`, which `editor` does not know. ### The dialog A centered bordered box on the canvas, in the same visual family as `helpOverlay`: rounded border in `theme.Heading`, themed background. ``` ╭────────────────────────────────╮ │ New note │ │ in ~/Humdrum/Inbox/ │ │ │ │ > my-new-note▌ │ │ │ │ Enter to create · Esc cancel │ ╰────────────────────────────────╯ ``` One component, two uses: | Use | Title | Prefill | Confirm | | ------ | ----------- | --------------------- | -------------------------------------- | | New | `New note` | picker query, if any | create empty file, open it | | Rename | `Rename` | current basename | `os.Rename`, rebind path | Behavior: - The target directory is displayed, so it is clear whether Ctrl+N (current dir) or Ctrl+B (inbox) is in play. - Enter on an existing name **opens** that file (new) or refuses with `Name taken` (rename). This matches today's `openNoteAt`. - Slashes nest subdirectories, via the existing `picker.NewNotePath` plus `MkdirAll`. - Enter with an empty input reports `Type a name first` and keeps the dialog open. - Esc returns to the previous mode. Nothing is written, and no unnamed buffer is left behind. ### The header The header occupies the existing three-row top pad (`canvasTopPad = 3`), so it costs no text rows and the editor's height is unchanged: ``` row 0 (blank pad) row 1 ██ my-note ████████████████████ ← header bar row 2 (blank pad) row 3+ document text (unchanged height) ``` - Spans the content column, aligned inside the canvas margins — the same width as the text, matching preview's `titleBar`. - Bold, `theme.Heading` background, `theme.LegibleText(Heading)` foreground, one space of padding each side, truncated with `…` when the name overflows. - Text is the basename minus `.md`. A frontmatter `title:` property does **not** override it: edit mode shows what is on disk. - A pathless buffer renders `Untitled` in `theme.Muted` rather than the heading bar, so "unsaved and unnamed" reads at a glance. - A dirty buffer appends a bullet: `my-note •`. The status bar keeps its own indicator. - Rendered in `ModeEditor` and the overlay modes that keep the editor visible beneath (find, goto, spell). Not in the picker, not in help (the body is replaced), and not in preview (which has its own title bar). ### Wiring - `ModeNamePrompt` is added. `ModeSaveAs`, `saveInput`, and `saveBar` are deleted; `saveAs()` folds into the dialog's confirm handler. - `newFile()` opens the dialog instead of calling `startBlankIn()`. The dirty-buffer discard confirmation stays in front of the dialog: press the same key again to discard, then the dialog opens. - Picker Ctrl+N opens the dialog prefilled with the typed query, cursor at the end. (Today it creates the file with no prompt.) - `glint -n` with no name boots straight into the dialog over an empty editor. - `F2` opens the rename dialog, prefilled with the current basename. On confirm: `os.Rename`, rebind `App.path`, reopen Harper under the new URI via `grammarOpen`, and update the preview title. On a pathless buffer, F2 names and writes it. - Ctrl+S on a pathless buffer opens the same dialog — one naming UI, not two. - `startBlankIn()` survives for the pathless case. ### Testing - `internal/dialog`: unit tests for validation, prefill, Esc, and slash-nested paths. - `internal/app`: header text and dirty-marker rendering; the F2 rename round-trip against a temp directory; the new-file flow from both the editor and the picker. - Existing `ModeSaveAs` tests are rewritten against the dialog.