chore: more cleanup in the specs

This commit is contained in:
2026-04-09 17:37:46 +02:00
parent 92f0687549
commit b7807161f0
10 changed files with 334 additions and 0 deletions

View File

@@ -37,6 +37,20 @@ value ModelSelectorDropdown {
selected_model_id: String?
}
surface ModelSelectorDropdownSurface {
context dropdown: ModelSelectorDropdown
exposes:
dropdown.selected_model_id when dropdown.selected_model_id != null
for group in dropdown.groups:
group.provider_name
for model in group.models:
model.model_id
model.display_name
model.context_window
model.max_output_tokens
}
value ModelProviderGroup {
provider_name: String -- e.g. "OpenAI", "Ollama", "LM Studio"
models: List<ModelEntry>

View File

@@ -47,6 +47,17 @@ value PostPickerOverlay {
overflow_count: Integer? -- shown as "and N more" if > 0
}
surface PostPickerOverlaySurface {
context overlay: PostPickerOverlay
exposes:
overlay.search_query
for result in overlay.results:
result.post_id
result.title
overlay.overflow_count when overlay.overflow_count != null
}
value PostPickerResult {
post_id: String
title: String

View File

@@ -73,6 +73,16 @@ value TranslationFlag {
is_active: Boolean -- true when this language is currently being edited
}
surface TranslationFlagSurface {
context flag: TranslationFlag
exposes:
flag.language
flag.flag_emoji
flag.status
flag.is_active
}
surface PostEditorSurface {
context editor: PostEditorView

View File

@@ -36,6 +36,15 @@ value ColourPickerPopover {
selected: String?
}
surface ColourPickerPopoverSurface {
context popover: ColourPickerPopover
exposes:
popover.presets
popover.custom_hex when popover.custom_hex != null
popover.selected when popover.selected != null
}
config {
colour_picker_preset_count: Integer = 17
tag_cloud_min_font: String = "0.85rem"

View File

@@ -17,6 +17,67 @@ surface FrontmatterPersistenceSurface {
PublishScriptRequested(script)
}
surface PostFrontmatterSurface {
context frontmatter: PostFrontmatter
exposes:
frontmatter.id
frontmatter.title
frontmatter.slug
frontmatter.status
frontmatter.published_at
frontmatter.tags
frontmatter.categories
}
surface MediaSidecarSurface {
context sidecar: MediaSidecar
exposes:
sidecar.id
sidecar.original_name
sidecar.mime_type
sidecar.width
sidecar.height
sidecar.updated_at
}
surface TemplateFrontmatterSurface {
context frontmatter: TemplateFrontmatter
exposes:
frontmatter.id
frontmatter.slug
frontmatter.kind
frontmatter.enabled
frontmatter.version
}
surface ScriptFrontmatterSurface {
context frontmatter: ScriptFrontmatter
exposes:
frontmatter.id
frontmatter.slug
frontmatter.kind
frontmatter.entrypoint
frontmatter.enabled
frontmatter.version
}
surface MenuOpmlSurface {
context document: MenuOpml
exposes:
document.header.title
document.header.date_created
document.header.date_modified
for item in document.body:
item.kind
item.label
item.slug
}
-- ============================================================================
-- POST FILE FORMAT
-- ============================================================================

View File

@@ -49,6 +49,20 @@ entity SiteGeneration {
generated_files: GeneratedFile with project_id = this.project_id
}
surface GenerationStatusSurface {
context generation: SiteGeneration
exposes:
generation.project_id
generation.base_url
generation.language
generation.blog_languages
generation.max_posts_per_page
generation.pico_theme
generation.sections
generation.generated_files.count
}
invariant IncrementalByContentHash {
-- Files are only written when content_hash changes
-- generatedFileHashes table tracks (projectId, relativePath, contentHash)
@@ -61,6 +75,21 @@ invariant MultiLanguageRoutes {
-- Each language subtree gets its own feeds and archives
}
invariant CanonicalBaseUrlConfigured {
for generation in SiteGenerations:
generation.base_url != ""
}
invariant NamedPicoTheme {
for generation in SiteGenerations where generation.pico_theme != null:
generation.pico_theme != ""
}
invariant GeneratedFilesTracked {
for generation in SiteGenerations:
generation.generated_files.count >= 0
}
-- Core section: root pages, sitemap, RSS, Atom, calendar.json
rule GenerateCoreSectionPages {

View File

@@ -98,6 +98,11 @@ invariant ThemeSwitching {
-- Uses Pico CSS with configurable themes
}
invariant PreviewServerBinding {
for server in PreviewServers where server.is_running:
server.host = config.preview_host and server.port = config.preview_port
}
invariant LocalhostOnly {
-- Preview server binds to 127.0.0.1 only, never 0.0.0.0
}

View File

@@ -58,6 +58,16 @@ value RelativeDateFormat {
else: timestamp.toLocaleDateString(locale, month: short, day: numeric)
}
surface RelativeDateFormatSurface {
context format: RelativeDateFormat
exposes:
format.timestamp
format.locale
format.diff_days
format.display
}
value PostDateFormat {
timestamp: Timestamp
locale: String
@@ -67,6 +77,15 @@ value PostDateFormat {
-- Example: "Feb 10, 2026"
}
surface PostDateFormatSurface {
context format: PostDateFormat
exposes:
format.timestamp
format.locale
format.display
}
value PostTypeIcon {
categories: List<String>
@@ -80,6 +99,14 @@ value PostTypeIcon {
else: "document"
}
surface PostTypeIconSurface {
context icon: PostTypeIcon
exposes:
icon.categories
icon.icon
}
invariant SidebarEntityListPattern {
-- Views following this pattern (scripts, templates, chat, import) must provide:
-- 1. Header with localised title and create button.
@@ -103,6 +130,22 @@ value PostsView {
has_more: Boolean -- pagination, 500 per batch
}
surface PostsViewSurface {
context view: PostsView
exposes:
view.mode
view.search_query
view.filter_panel_visible
view.calendar_filter when view.calendar_filter != null
view.tag_filter
view.category_filter
view.draft_section.count
view.published_section.count
view.archived_section.count
view.has_more
}
-- Drafts section always shows all drafts regardless of filters.
-- Published and archived sections respect active filters.
-- "Clear All Filters" button resets search, date, tags, categories.
@@ -175,6 +218,17 @@ value MediaView {
grid: List<MediaGridItem> -- grid layout (not list)
}
surface MediaViewSurface {
context view: MediaView
exposes:
view.search_query
view.filter_panel_visible
view.calendar_filter when view.calendar_filter != null
view.tag_filter
view.grid.count
}
value MediaGridItem {
media_id: String
thumbnail_path: String? -- small (150px) thumbnail on disk when image; null for non-image
@@ -240,6 +294,13 @@ value ScriptsView {
items: List<ScriptListItem>
}
surface ScriptsViewSurface {
context view: ScriptsView
exposes:
view.items.count
}
value ScriptListItem {
script_id: String
title: String -- truncated with ellipsis on overflow
@@ -299,6 +360,13 @@ value TemplatesView {
items: List<TemplateListItem>
}
surface TemplatesViewSurface {
context view: TemplatesView
exposes:
view.items.count
}
value TemplateListItem {
template_id: String
title: String -- truncated with ellipsis on overflow
@@ -398,6 +466,13 @@ value SettingsNav {
active_section: String? -- persisted across sidebar switches
}
surface SettingsNavSurface {
context nav: SettingsNav
exposes:
nav.active_section when nav.active_section != null
}
rule SettingsNavClick {
when: SettingsNavEntryClicked(section)
if section = style:
@@ -452,6 +527,13 @@ value TagsNav {
active_section: String? -- persisted
}
surface TagsNavSurface {
context nav: TagsNav
exposes:
nav.active_section when nav.active_section != null
}
rule TagsNavClick {
when: TagsNavEntryClicked(section)
ensures: OpenTabRequested(type: tags, id: tags, intent: pin)
@@ -469,6 +551,14 @@ value ChatView {
items: List<ChatListItem>
}
surface ChatViewSurface {
context view: ChatView
exposes:
view.api_ready
view.items.count
}
value ChatListItem {
conversation_id: String
title: String -- live-updated via onTitleUpdated
@@ -514,6 +604,13 @@ value ImportView {
items: List<ImportListItem>
}
surface ImportViewSurface {
context view: ImportView
exposes:
view.items.count
}
value ImportListItem {
definition_id: String
name: String -- live-updated via onNameUpdated
@@ -571,6 +668,22 @@ value GitActiveView {
has_more_history: Boolean -- paginated, 20 per page
}
surface GitActiveViewSurface {
context view: GitActiveView
exposes:
view.branch
view.upstream when view.upstream != null
view.ahead
view.behind
view.status_files.count
view.history_entries.count
view.has_more_history
provides:
GitCommitRequested(message)
}
value GitStatusFile {
path: String
status: String -- modified, added, deleted, renamed, etc.
@@ -672,6 +785,14 @@ value CalendarYear {
months: List<CalendarMonth>
}
surface CalendarYearSurface {
context calendar_year: CalendarYear
exposes:
calendar_year.year
calendar_year.months.count
}
value CalendarMonth {
month: Integer -- 1-12
count: Integer -- number of items in this month

View File

@@ -8,6 +8,25 @@
use "./layout.allium" as layout
surface TabControlSurface {
facing _: TabOperator
provides:
OpenTabRequested(type, id, intent)
OpenTabInBackgroundRequested(type, id, intent)
CloseTabRequested(tab)
PinTabRequested(tab)
ClearTabsRequested()
}
surface TabRuntimeSurface {
facing _: TabRuntime
provides:
TabOpening(tab_type, intent)
ActiveTabChanged(active_tab)
}
-- ─── Tab types ────────────────────────────────────────────────
-- 17 distinct tab types, each routing to a matching editor view.
@@ -29,6 +48,15 @@ value Tab {
is_transient: Boolean -- true = preview tab (italic title, replaceable)
}
surface TabSurface {
context tab: Tab
exposes:
tab.type
tab.id
tab.is_transient
}
-- ─── Tab categories ───────────────────────────────────────────
-- 1. Singleton tool tabs: always one instance, never transient, id = type name.
@@ -161,6 +189,16 @@ value TabBarItem {
is_dirty: Boolean -- dot indicator, only for post tabs
}
surface TabBarItemSurface {
context item: TabBarItem
exposes:
item.title
item.is_active
item.is_transient
item.is_dirty
}
-- Tab title resolution:
-- post: post.title from DB (listens post-updated events); no JS truncation
-- media: media.originalName; no JS truncation

View File

@@ -21,6 +21,42 @@ surface TemplateRenderingSurface {
RenderNotFoundPageRequested()
}
surface RenderContextSurface {
context context: RenderContext
exposes:
context.language
context.language_prefix
context.page_title
context.pico_stylesheet_href
context.blog_languages
context.alternate_links
context.menu_items
context.post
context.day_blocks
context.archive_context
context.is_list_page
context.prev_page_href
context.next_page_href
context.not_found_message
}
surface PaginationContextSurface {
context pagination: PaginationContext
exposes:
pagination.is_first_page
pagination.is_last_page
pagination.has_prev_page
pagination.has_next_page
pagination.prev_page_href
pagination.next_page_href
pagination.current_page
pagination.total_pages
pagination.total_items
pagination.items_per_page
}
value RenderContext {
-- Top-level variables available in all templates
language: String -- Current language code