▍ humdrum codex / glint v1.1.2
license AGPL-3.0

docs: design for new-file naming dialog & editor filename header (TASK-046)

07a496f33b9caf1b97d0a47dde119e276e311383
humdrum-tiv <45084903+humdrum-tiv@users.noreply.github.com> · 2026-07-30 17:34

parent fe49582b

docs: design for new-file naming dialog & editor filename header (TASK-046)

Ask for the filename up front in a centered dialog instead of deferring to a
bottom-bar save-as prompt, and show the filename in editor mode as an
uneditable header bar styled like preview's title bar. One dialog component
(new internal/dialog package) serves new-file, name-this-buffer, and F2 rename;
ModeSaveAs goes away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

2 files changed

- → New-file-naming-dialog-editor-filename-header.md +29 −0
@@ -0,0 +1,29 @@
+---
+id: TASK-046
+title: New-file naming dialog + editor filename header
+status: "\U0001F7E6 Backlog"
+assignee: []
+created_date: '2026-07-31 00:34'
+labels:
+  - feature
+dependencies: []
+priority: medium
+ordinal: 45000
+---
+
+## Description
+
+<!-- SECTION:DESCRIPTION:BEGIN -->
+Ask for the filename up front in a centered dialog whenever a new file is created, and show the filename in editor mode as an uneditable header bar styled like preview's title bar. Collapses save-as/new-file into one dialog component (new internal/dialog package) and reuses it for F2 rename. Design: docs/superpowers/specs/2026-07-30-new-file-dialog-design.md
+<!-- SECTION:DESCRIPTION:END -->
+
+## Acceptance Criteria
+<!-- AC:BEGIN -->
+- [ ] #1 Ctrl+N/Ctrl+B, picker Ctrl+N (prefilled with query), and 'glint -n' with no name all open a centered naming dialog
+- [ ] #2 Enter creates the file immediately and opens it; existing name opens that file; slashes nest subdirs; empty name reports 'Type a name first'
+- [ ] #3 Esc cancels back to the previous mode with nothing written
+- [ ] #4 Editor mode shows the basename (minus .md) as a full-width header bar in the top pad, costing no text rows
+- [ ] #5 Pathless buffer shows muted 'Untitled'; dirty buffer appends a bullet
+- [ ] #6 F2 renames the current file on disk, rebinds the path, and reopens Harper under the new URI
+- [ ] #7 ModeSaveAs/saveInput/saveBar removed; Ctrl+S on a pathless buffer opens the same dialog
+<!-- AC:END -->
docs/superpowers/specs/2026-07-30-new-file-dialog-design.md +127 −0
@@ -0,0 +1,127 @@
+# 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.