The phone's rows learn their faces and two more orders — icons everywhere, lanes and cards sortable
Boards, lanes, and cards all show their frontmatter icon leading the row, tinted through the palette with a secondary fallback; the board scanner now reads icon and iconColor from the same one-file parse as the title. Lane and card lists gain the segmented Manual/Name/Recent control — display-only sorting layered over the rank order, never rewriting what's on disk. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -17,6 +17,14 @@ struct BoardSummary: Identifiable, Sendable, Equatable {
|
||||
/// no `title` key is a normal board rather than an untitled one.
|
||||
let title: String
|
||||
|
||||
/// The board `index.md`'s `icon:`, verbatim — an SF Symbol name, or `nil` when unset or the file
|
||||
/// couldn't be read. Unlike `title`, there is no fallback: a board with no icon shows none.
|
||||
let icon: String?
|
||||
|
||||
/// The board `index.md`'s `iconColor:`, verbatim — a `CardPalette` swatch name, or `nil` on the
|
||||
/// same terms as `icon`.
|
||||
let iconColor: String?
|
||||
|
||||
/// Lanes the loader would show. `nil` where the package is not materialized enough to count —
|
||||
/// see `BoardSummaryScanner` for why a number is withheld rather than guessed at zero.
|
||||
let laneCount: Int?
|
||||
@@ -172,6 +180,8 @@ enum BoardSummaryScanner {
|
||||
return BoardSummary(
|
||||
rootURL: entry.rootURL,
|
||||
title: fallbackTitle,
|
||||
icon: nil,
|
||||
iconColor: nil,
|
||||
laneCount: nil,
|
||||
cardCount: nil,
|
||||
modified: entry.modified,
|
||||
@@ -181,7 +191,8 @@ enum BoardSummaryScanner {
|
||||
}
|
||||
|
||||
let indexURL = entry.rootURL.appendingPathComponent(IntegrityRules.indexFileName)
|
||||
let title = readTitle(at: indexURL) ?? fallbackTitle
|
||||
let fields = readIndexFields(at: indexURL)
|
||||
let title = fields.title ?? fallbackTitle
|
||||
|
||||
// An unreadable board `index.md` means this is not a board the loader would open — a package
|
||||
// still arriving, or one whose root file is genuinely broken. Either way a count would be a
|
||||
@@ -190,6 +201,8 @@ enum BoardSummaryScanner {
|
||||
return BoardSummary(
|
||||
rootURL: entry.rootURL,
|
||||
title: title,
|
||||
icon: fields.icon,
|
||||
iconColor: fields.iconColor,
|
||||
laneCount: nil,
|
||||
cardCount: nil,
|
||||
modified: entry.modified,
|
||||
@@ -208,6 +221,8 @@ enum BoardSummaryScanner {
|
||||
return BoardSummary(
|
||||
rootURL: entry.rootURL,
|
||||
title: title,
|
||||
icon: fields.icon,
|
||||
iconColor: fields.iconColor,
|
||||
laneCount: lanes,
|
||||
cardCount: cards,
|
||||
modified: entry.modified,
|
||||
@@ -216,18 +231,22 @@ enum BoardSummaryScanner {
|
||||
)
|
||||
}
|
||||
|
||||
/// The board title as written, or `nil` where the file is absent, is not UTF-8, has no
|
||||
/// frontmatter, or carries no usable `title:` — every one of which is the folder-name fallback.
|
||||
private nonisolated static func readTitle(at indexURL: URL) -> String? {
|
||||
/// The board `index.md`'s `title:`/`icon:`/`iconColor:`, read together since they come from the
|
||||
/// same one-file parse. `title` is `nil` where the file is absent, is not UTF-8, has no
|
||||
/// frontmatter, or carries no usable `title:` — every one of which is the folder-name fallback in
|
||||
/// `scan`. `icon`/`iconColor` are `nil` on the same unreadable-file terms, and also whenever the
|
||||
/// document simply doesn't set them — there is no fallback for either.
|
||||
private nonisolated static func readIndexFields(
|
||||
at indexURL: URL
|
||||
) -> (title: String?, icon: String?, iconColor: String?) {
|
||||
guard let data = try? Data(contentsOf: indexURL),
|
||||
let text = String(data: data, encoding: .utf8),
|
||||
let document = try? FrontmatterDocument.parse(text),
|
||||
let title = document.title.value,
|
||||
!title.isEmpty
|
||||
let document = try? FrontmatterDocument.parse(text)
|
||||
else {
|
||||
return nil
|
||||
return (nil, nil, nil)
|
||||
}
|
||||
return title
|
||||
let title = document.title.value
|
||||
return (title?.isEmpty == false ? title : nil, document.icon.value, document.iconColor.value)
|
||||
}
|
||||
|
||||
/// Direct subfolders that are lanes or cards by the loader's own two gates, reached through the
|
||||
|
||||
Reference in New Issue
Block a user