Comments, phase 2 — the pane, the composer, and the inline session
The card window recomposes into three componentized panes (body, comments, attributes) with two mounts — beside or body-over-comments at ~3:2 — behind View ▸ Comments Beside Body. View ▸ Show Comments is one persisted app-wide bit, no content-derived auto-show; File ▸ Add Comment flips it on and focuses the composer. The thread renders author lines, edited markers, card-subset Markdown bodies, and read-only Quick Look chips under a count header with the sort- direction control. The composer edits comments/.draft/ on the slow cadence (blur, close, quit, ~30s interval), Escape only moves focus, ⌘↩ posts. Inline edit is a body-edit session in miniature: 700ms debounce, Save/⌘↩ commits, Cancel and Escape revert to session-start bytes, close flushes. File drops within either authoring surface carve out of the window-wide card default into that surface's attachments/; paperclips cover the no-drag path. Close flush runs inline flush, then draft save, then the comments/.trash purge; open sweeps crash residue. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -119,6 +119,61 @@ enum CardWindowMetrics {
|
||||
previewPadding(bodyPointSize: bodyPointSize)
|
||||
}
|
||||
|
||||
// MARK: - The comments pane
|
||||
|
||||
/// How wide the comments pane is when it is mounted **beside** the body, in characters
|
||||
/// (05-card-window.md ▸ Composition; ▸ The comments column).
|
||||
///
|
||||
/// Wider than the sidebar and narrower than the body's default measure, which is what it holds:
|
||||
/// a rendered Markdown paragraph, an author line, a wrapping chip or two, and a composer. It is a
|
||||
/// *fixed* width for the sidebar's reason — "resize flex always goes to the body, never the fixed
|
||||
/// panes" (05 ▸ Composition) — so this is not a fraction of anything either.
|
||||
///
|
||||
/// Stacked, the pane takes the body's width instead and this number is not consulted at all,
|
||||
/// which is why the window's minimum grows only in the beside mount (`CommentsMount.widensWindow`).
|
||||
static let commentsColumnCharacters: CGFloat = 40
|
||||
|
||||
static func commentsColumnWidth(bodyPointSize: CGFloat) -> CGFloat {
|
||||
columnWidth(characters: commentsColumnCharacters, bodyPointSize: bodyPointSize)
|
||||
}
|
||||
|
||||
/// The narrowest the comments pane is allowed to get — the floor its share of the window's
|
||||
/// minimum is measured at, a shorter measure than the body's because a comment is a remark rather
|
||||
/// than a document.
|
||||
static let commentsMinimumCharacters: CGFloat = 28
|
||||
|
||||
static func commentsMinimumWidth(bodyPointSize: CGFloat) -> CGFloat {
|
||||
columnWidth(characters: commentsMinimumCharacters, bodyPointSize: bodyPointSize)
|
||||
}
|
||||
|
||||
/// The smallest an attachment chip may be before the row wraps — a thumbnail, a few characters of
|
||||
/// filename, and the padding around them. Middle truncation does the rest, so a long name shrinks
|
||||
/// rather than widening the pane.
|
||||
static func commentChipMinimumWidth(bodyPointSize: CGFloat) -> CGFloat {
|
||||
(attachmentThumbnailSide(bodyPointSize: bodyPointSize) + bodyPointSize * 6).rounded()
|
||||
}
|
||||
|
||||
/// The composer's resting height — **four lines and a bit**, which is the shape of the thing it
|
||||
/// invites: enough that a two-sentence remark is visible whole, short enough that it never
|
||||
/// dominates a thread. It scrolls internally past that rather than growing the pane, so a long
|
||||
/// draft cannot push the thread off screen.
|
||||
static func composerHeight(bodyPointSize: CGFloat) -> CGFloat {
|
||||
(lineHeight(bodyPointSize: bodyPointSize) * 4.5).rounded()
|
||||
}
|
||||
|
||||
/// An inline edit session's editor, one line taller than the composer: it opens over text that
|
||||
/// already exists, so the common case is reading it before changing it.
|
||||
static func inlineEditorHeight(bodyPointSize: CGFloat) -> CGFloat {
|
||||
(lineHeight(bodyPointSize: bodyPointSize) * 5.5).rounded()
|
||||
}
|
||||
|
||||
/// The gap between two comments in the thread — a full gutter, one step larger than the rhythm
|
||||
/// *inside* a comment (`sidebarRowSpacing`), so the eye groups an author line with its body
|
||||
/// rather than with its neighbour.
|
||||
static func commentSpacing(bodyPointSize: CGFloat) -> CGFloat {
|
||||
gutter(bodyPointSize: bodyPointSize)
|
||||
}
|
||||
|
||||
// MARK: - The rendered body
|
||||
|
||||
/// One step of structural indent in Preview — a list level, a quote level. One and a half ems,
|
||||
@@ -149,9 +204,22 @@ enum CardWindowMetrics {
|
||||
|
||||
/// The window's minimum size: the sidebar's fixed width plus the body's floor, and tall enough
|
||||
/// for a title, its date line and a few lines of body.
|
||||
static func minimumSize(bodyPointSize: CGFloat) -> CGSize {
|
||||
CGSize(
|
||||
width: sidebarWidth(bodyPointSize: bodyPointSize) + bodyMinimumWidth(bodyPointSize: bodyPointSize),
|
||||
///
|
||||
/// - Parameter commentsColumn: whether the comments pane is currently mounted **beside** the body
|
||||
/// — "the window's minimum width grows only while the column is shown side-by-side"
|
||||
/// (05-card-window.md ▸ Composition). Stacked, or hidden, the pane costs the window no width at
|
||||
/// all, which is the narrow-display case the layout option exists for. The height is unchanged
|
||||
/// either way: a stacked pane divides the height it is given rather than demanding more, and a
|
||||
/// window at its minimum height simply gets a short thread.
|
||||
///
|
||||
/// Defaulted to `false` so every caller that predates the comments pane still asks the same
|
||||
/// question it always did.
|
||||
static func minimumSize(bodyPointSize: CGFloat, commentsColumn: Bool = false) -> CGSize {
|
||||
let comments = commentsColumn ? commentsMinimumWidth(bodyPointSize: bodyPointSize) : 0
|
||||
return CGSize(
|
||||
width: sidebarWidth(bodyPointSize: bodyPointSize)
|
||||
+ bodyMinimumWidth(bodyPointSize: bodyPointSize)
|
||||
+ comments,
|
||||
height: (lineHeight(bodyPointSize: bodyPointSize) * 16).rounded()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user