chore: and more cleanups
This commit is contained in:
@@ -19,6 +19,16 @@ entity AiEndpoint {
|
|||||||
-- airplane: local model (Ollama, LM Studio, etc.)
|
-- airplane: local model (Ollama, LM Studio, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface AiEndpointSurface {
|
||||||
|
context endpoint: AiEndpoint
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
endpoint.kind
|
||||||
|
endpoint.url
|
||||||
|
endpoint.api_key when endpoint.api_key != null
|
||||||
|
endpoint.model
|
||||||
|
}
|
||||||
|
|
||||||
entity SecureKeyStore {
|
entity SecureKeyStore {
|
||||||
-- Encrypts API keys using OS keychain
|
-- Encrypts API keys using OS keychain
|
||||||
-- macOS: Keychain, Windows: DPAPI, Linux: libsecret
|
-- macOS: Keychain, Windows: DPAPI, Linux: libsecret
|
||||||
@@ -26,6 +36,10 @@ entity SecureKeyStore {
|
|||||||
-- No plain-text fallback
|
-- No plain-text fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface SecureKeyStoreSurface {
|
||||||
|
context _: SecureKeyStore
|
||||||
|
}
|
||||||
|
|
||||||
entity ChatConversation {
|
entity ChatConversation {
|
||||||
title: String
|
title: String
|
||||||
model: String
|
model: String
|
||||||
@@ -35,6 +49,17 @@ entity ChatConversation {
|
|||||||
messages: ChatMessage with conversation = this
|
messages: ChatMessage with conversation = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ChatConversationSurface {
|
||||||
|
context conversation: ChatConversation
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
conversation.title
|
||||||
|
conversation.model
|
||||||
|
conversation.created_at
|
||||||
|
conversation.updated_at
|
||||||
|
conversation.messages.count
|
||||||
|
}
|
||||||
|
|
||||||
entity ChatMessage {
|
entity ChatMessage {
|
||||||
conversation: ChatConversation
|
conversation: ChatConversation
|
||||||
role: system | user | assistant | tool
|
role: system | user | assistant | tool
|
||||||
@@ -44,6 +69,18 @@ entity ChatMessage {
|
|||||||
created_at: Timestamp
|
created_at: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ChatMessageSurface {
|
||||||
|
context message: ChatMessage
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
message.conversation
|
||||||
|
message.role
|
||||||
|
message.content
|
||||||
|
message.token_usage_input when message.token_usage_input != null
|
||||||
|
message.token_usage_output when message.token_usage_output != null
|
||||||
|
message.created_at
|
||||||
|
}
|
||||||
|
|
||||||
surface OneShotAiSurface {
|
surface OneShotAiSurface {
|
||||||
facing _: AiOperator
|
facing _: AiOperator
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ value GitSyncStatus {
|
|||||||
kind: local_only | remote_only | both
|
kind: local_only | remote_only | both
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface GitSyncStatusSurface {
|
||||||
|
context status: GitSyncStatus
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
status.kind
|
||||||
|
}
|
||||||
|
|
||||||
entity GitRepository {
|
entity GitRepository {
|
||||||
is_initialized: Boolean
|
is_initialized: Boolean
|
||||||
remote_url: String?
|
remote_url: String?
|
||||||
@@ -25,6 +32,17 @@ entity GitRepository {
|
|||||||
has_lfs: Boolean
|
has_lfs: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface GitRepositorySurface {
|
||||||
|
context repo: GitRepository
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
repo.is_initialized
|
||||||
|
repo.remote_url when repo.remote_url != null
|
||||||
|
repo.provider when repo.provider != null
|
||||||
|
repo.current_branch when repo.current_branch != null
|
||||||
|
repo.has_lfs
|
||||||
|
}
|
||||||
|
|
||||||
surface GitControlSurface {
|
surface GitControlSurface {
|
||||||
facing _: GitOperator
|
facing _: GitOperator
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,24 @@
|
|||||||
use "./i18n.allium" as i18n
|
use "./i18n.allium" as i18n
|
||||||
use "./task.allium" as task
|
use "./task.allium" as task
|
||||||
|
|
||||||
|
surface LayoutControlSurface {
|
||||||
|
facing _: LayoutOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
ToggleSidebarRequested()
|
||||||
|
TogglePanelRequested()
|
||||||
|
ToggleAssistantSidebarRequested()
|
||||||
|
ActivityClicked(activity_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface LayoutRuntimeSurface {
|
||||||
|
facing _: LayoutRuntime
|
||||||
|
|
||||||
|
provides:
|
||||||
|
GitBadgePollTick(badge)
|
||||||
|
ClearGitBadgeTick(badge)
|
||||||
|
}
|
||||||
|
|
||||||
-- ─── Window shell ─────────────────────────────────────────────
|
-- ─── Window shell ─────────────────────────────────────────────
|
||||||
|
|
||||||
-- +------------------------------------------------------------+
|
-- +------------------------------------------------------------+
|
||||||
@@ -37,6 +55,17 @@ value AppShell {
|
|||||||
status_bar: StatusBar
|
status_bar: StatusBar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface AppShellSurface {
|
||||||
|
context shell: AppShell
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
shell.title_bar.title
|
||||||
|
shell.sidebar.visible
|
||||||
|
shell.sidebar.width
|
||||||
|
shell.content_area.panel.visible
|
||||||
|
shell.assistant_sidebar.visible
|
||||||
|
}
|
||||||
|
|
||||||
value ContentArea {
|
value ContentArea {
|
||||||
-- tab_bar: see tabs.allium
|
-- tab_bar: see tabs.allium
|
||||||
-- editor: routed by active tab; see tabs.allium
|
-- editor: routed by active tab; see tabs.allium
|
||||||
@@ -69,6 +98,15 @@ value ShellVisibility {
|
|||||||
assistant_sidebar_visible: Boolean
|
assistant_sidebar_visible: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ShellVisibilitySurface {
|
||||||
|
context visibility: ShellVisibility
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
visibility.sidebar_visible
|
||||||
|
visibility.panel_visible
|
||||||
|
visibility.assistant_sidebar_visible
|
||||||
|
}
|
||||||
|
|
||||||
rule ToggleSidebar {
|
rule ToggleSidebar {
|
||||||
when: ToggleSidebarRequested()
|
when: ToggleSidebarRequested()
|
||||||
ensures: sidebar_visible = not sidebar_visible
|
ensures: sidebar_visible = not sidebar_visible
|
||||||
|
|||||||
@@ -15,6 +15,16 @@ entity McpServer {
|
|||||||
is_running: Boolean
|
is_running: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface McpServerSurface {
|
||||||
|
context server: McpServer
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
server.transport
|
||||||
|
server.host
|
||||||
|
server.port
|
||||||
|
server.is_running
|
||||||
|
}
|
||||||
|
|
||||||
entity Proposal {
|
entity Proposal {
|
||||||
kind: draft_post | propose_script | propose_template | propose_media_metadata | propose_post_metadata
|
kind: draft_post | propose_script | propose_template | propose_media_metadata | propose_post_metadata
|
||||||
status: pending | accepted | discarded | expired
|
status: pending | accepted | discarded | expired
|
||||||
@@ -38,6 +48,24 @@ entity Proposal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ProposalSurface {
|
||||||
|
context proposal: Proposal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
proposal.kind
|
||||||
|
proposal.status
|
||||||
|
proposal.entity_id
|
||||||
|
proposal.data
|
||||||
|
proposal.created_at
|
||||||
|
proposal.expires_at
|
||||||
|
proposal.draft_post when proposal.draft_post != null
|
||||||
|
proposal.proposed_script when proposal.proposed_script != null
|
||||||
|
proposal.proposed_template when proposal.proposed_template != null
|
||||||
|
proposal.target_media when proposal.target_media != null
|
||||||
|
proposal.target_post when proposal.target_post != null
|
||||||
|
proposal.is_expired
|
||||||
|
}
|
||||||
|
|
||||||
config {
|
config {
|
||||||
http_port: Integer = 4124
|
http_port: Integer = 4124
|
||||||
proposal_ttl_app: Duration = 30.minutes
|
proposal_ttl_app: Duration = 30.minutes
|
||||||
@@ -339,6 +367,13 @@ value McpAgentKind {
|
|||||||
kind: String
|
kind: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface McpAgentKindSurface {
|
||||||
|
context agent_kind: McpAgentKind
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
agent_kind.kind
|
||||||
|
}
|
||||||
|
|
||||||
rule InstallAgentConfig {
|
rule InstallAgentConfig {
|
||||||
when: InstallAgentConfigRequested(agent_kind)
|
when: InstallAgentConfigRequested(agent_kind)
|
||||||
-- Writes stdio MCP server config into the agent's config file
|
-- Writes stdio MCP server config into the agent's config file
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
|
|
||||||
use "./project.allium" as project
|
use "./project.allium" as project
|
||||||
|
|
||||||
|
surface MediaControlSurface {
|
||||||
|
facing _: MediaOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
ImportMediaRequested(project, source_file)
|
||||||
|
UpdateMediaRequested(media, changes)
|
||||||
|
DeleteMediaRequested(media)
|
||||||
|
UpsertMediaTranslationRequested(media, language, title, alt, caption)
|
||||||
|
RebuildMediaFromFilesRequested(project)
|
||||||
|
}
|
||||||
|
|
||||||
value ThumbnailSet {
|
value ThumbnailSet {
|
||||||
small: String -- 150px width (binary path)
|
small: String -- 150px width (binary path)
|
||||||
medium: String -- 400px width (binary path)
|
medium: String -- 400px width (binary path)
|
||||||
@@ -19,6 +30,13 @@ value SidecarFile {
|
|||||||
path: String
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface SidecarFileSurface {
|
||||||
|
context sidecar: SidecarFile
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
sidecar.path
|
||||||
|
}
|
||||||
|
|
||||||
entity Media {
|
entity Media {
|
||||||
project: project/Project
|
project: project/Project
|
||||||
filename: String
|
filename: String
|
||||||
@@ -48,6 +66,37 @@ entity Media {
|
|||||||
thumbnails: ThumbnailSet
|
thumbnails: ThumbnailSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface MediaSurface {
|
||||||
|
context media: Media
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
media.project
|
||||||
|
media.filename
|
||||||
|
media.original_name
|
||||||
|
media.mime_type
|
||||||
|
media.size
|
||||||
|
media.width when media.width != null
|
||||||
|
media.height when media.height != null
|
||||||
|
media.title when media.title != null
|
||||||
|
media.alt when media.alt != null
|
||||||
|
media.caption when media.caption != null
|
||||||
|
media.author when media.author != null
|
||||||
|
media.language when media.language != null
|
||||||
|
media.file_path
|
||||||
|
media.sidecar_path
|
||||||
|
media.checksum when media.checksum != null
|
||||||
|
media.tags
|
||||||
|
media.created_at
|
||||||
|
media.updated_at
|
||||||
|
media.translations.count
|
||||||
|
media.linked_posts.count
|
||||||
|
media.available_languages
|
||||||
|
media.thumbnails.small
|
||||||
|
media.thumbnails.medium
|
||||||
|
media.thumbnails.large
|
||||||
|
media.thumbnails.ai
|
||||||
|
}
|
||||||
|
|
||||||
entity MediaTranslation {
|
entity MediaTranslation {
|
||||||
media: Media
|
media: Media
|
||||||
language: String
|
language: String
|
||||||
|
|||||||
@@ -10,6 +10,23 @@
|
|||||||
use "./media.allium" as media
|
use "./media.allium" as media
|
||||||
use "./search.allium" as search
|
use "./search.allium" as search
|
||||||
|
|
||||||
|
surface MediaProcessingControlSurface {
|
||||||
|
facing _: MediaProcessingOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
ImportMediaRequested(source_path, project)
|
||||||
|
TagMediaRequested(media, tags)
|
||||||
|
DeleteMediaRequested(media)
|
||||||
|
ValidateMediaRequested(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface MediaProcessingRuntimeSurface {
|
||||||
|
facing _: MediaProcessingRuntime
|
||||||
|
|
||||||
|
provides:
|
||||||
|
MediaImported(media)
|
||||||
|
}
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- MEDIA FILE ORGANIZATION
|
-- MEDIA FILE ORGANIZATION
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
@@ -27,6 +44,18 @@ value MediaFileLayout {
|
|||||||
thumbnail_ai: String -- thumbnails/{prefix}/{id}-ai.jpg
|
thumbnail_ai: String -- thumbnails/{prefix}/{id}-ai.jpg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface MediaFileLayoutSurface {
|
||||||
|
context layout: MediaFileLayout
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
layout.binary_path
|
||||||
|
layout.sidecar_path
|
||||||
|
layout.thumbnail_small
|
||||||
|
layout.thumbnail_medium
|
||||||
|
layout.thumbnail_large
|
||||||
|
layout.thumbnail_ai
|
||||||
|
}
|
||||||
|
|
||||||
invariant MediaFileNaming {
|
invariant MediaFileNaming {
|
||||||
-- Original filename is preserved in original_name field
|
-- Original filename is preserved in original_name field
|
||||||
-- Stored filename uses UUID v4: {uuid}.{ext}
|
-- Stored filename uses UUID v4: {uuid}.{ext}
|
||||||
@@ -109,6 +138,10 @@ value ThumbnailGeneration {
|
|||||||
-- No _source copy is made. Thumbnails regenerated from the original binary.
|
-- No _source copy is made. Thumbnails regenerated from the original binary.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ThumbnailGenerationSurface {
|
||||||
|
context _: ThumbnailGeneration
|
||||||
|
}
|
||||||
|
|
||||||
invariant ThumbnailExifHandling {
|
invariant ThumbnailExifHandling {
|
||||||
-- EXIF orientation IS applied during thumbnail generation so that
|
-- EXIF orientation IS applied during thumbnail generation so that
|
||||||
-- thumbnails always appear right-side-up regardless of camera metadata.
|
-- thumbnails always appear right-side-up regardless of camera metadata.
|
||||||
@@ -140,6 +173,14 @@ value ImageProcessing {
|
|||||||
-- 4. EXIF data is not stripped (thumbnails are re-encoded, so EXIF is naturally absent)
|
-- 4. EXIF data is not stripped (thumbnails are re-encoded, so EXIF is naturally absent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ImageProcessingSurface {
|
||||||
|
context processing: ImageProcessing
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
processing.input_formats
|
||||||
|
processing.output_formats
|
||||||
|
}
|
||||||
|
|
||||||
rule ProcessImageMetadata {
|
rule ProcessImageMetadata {
|
||||||
when: MediaImported(media)
|
when: MediaImported(media)
|
||||||
-- Extract image metadata from raw file header
|
-- Extract image metadata from raw file header
|
||||||
@@ -168,6 +209,17 @@ value MediaTranslationFile {
|
|||||||
caption: String?
|
caption: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface MediaTranslationFileSurface {
|
||||||
|
context file: MediaTranslationFile
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
file.translation_for
|
||||||
|
file.language
|
||||||
|
file.title when file.title != null
|
||||||
|
file.alt when file.alt != null
|
||||||
|
file.caption when file.caption != null
|
||||||
|
}
|
||||||
|
|
||||||
invariant MediaTranslationFileLayout {
|
invariant MediaTranslationFileLayout {
|
||||||
for t in MediaTranslations:
|
for t in MediaTranslations:
|
||||||
-- Translation sidecars sit next to the binary, with language suffix
|
-- Translation sidecars sit next to the binary, with language suffix
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
-- Scope: core (read for rendering), extension Bucket F (menu editor UI)
|
-- Scope: core (read for rendering), extension Bucket F (menu editor UI)
|
||||||
-- Distilled from: src/main/engine/MenuEngine.ts
|
-- Distilled from: src/main/engine/MenuEngine.ts
|
||||||
|
|
||||||
|
surface MenuManagementSurface {
|
||||||
|
facing _: MenuOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
UpdateMenuRequested(menu, items)
|
||||||
|
}
|
||||||
|
|
||||||
value MenuItem {
|
value MenuItem {
|
||||||
kind: page | submenu | category_archive | home
|
kind: page | submenu | category_archive | home
|
||||||
label: String
|
label: String
|
||||||
@@ -18,6 +25,15 @@ entity Menu {
|
|||||||
home_entry: home_items.first
|
home_entry: home_items.first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface MenuSurface {
|
||||||
|
context menu: Menu
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
menu.items.count
|
||||||
|
menu.home_items.count
|
||||||
|
menu.home_entry.label
|
||||||
|
}
|
||||||
|
|
||||||
invariant HomeAlwaysPresent {
|
invariant HomeAlwaysPresent {
|
||||||
-- The menu always has a Home entry, extracted and prepended
|
-- The menu always has a Home entry, extracted and prepended
|
||||||
for menu in Menus:
|
for menu in Menus:
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ value AISuggestionsModal {
|
|||||||
-- Cancel discards all; Apply writes only accepted fields to entity
|
-- Cancel discards all; Apply writes only accepted fields to entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface AISuggestionsModalSurface {
|
||||||
|
context modal: AISuggestionsModal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
modal.fields.count
|
||||||
|
}
|
||||||
|
|
||||||
value AISuggestionField {
|
value AISuggestionField {
|
||||||
label: String
|
label: String
|
||||||
current_value: String
|
current_value: String
|
||||||
@@ -65,6 +72,16 @@ value InsertPostLinkModal {
|
|||||||
related_posts: List<InsertLinkResult> -- similarity-based, shown when query empty
|
related_posts: List<InsertLinkResult> -- similarity-based, shown when query empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface InsertPostLinkModalSurface {
|
||||||
|
context modal: InsertPostLinkModal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
modal.active_tab
|
||||||
|
modal.search_query
|
||||||
|
modal.results.count
|
||||||
|
modal.related_posts.count
|
||||||
|
}
|
||||||
|
|
||||||
value InsertLinkResult {
|
value InsertLinkResult {
|
||||||
post_id: String
|
post_id: String
|
||||||
title: String
|
title: String
|
||||||
@@ -87,6 +104,14 @@ value InsertMediaModal {
|
|||||||
results: List<InsertMediaResult>
|
results: List<InsertMediaResult>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface InsertMediaModalSurface {
|
||||||
|
context modal: InsertMediaModal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
modal.search_query
|
||||||
|
modal.results.count
|
||||||
|
}
|
||||||
|
|
||||||
value InsertMediaResult {
|
value InsertMediaResult {
|
||||||
media_id: String
|
media_id: String
|
||||||
title: String
|
title: String
|
||||||
@@ -108,6 +133,14 @@ value LanguagePickerModal {
|
|||||||
available_targets: List<LanguageTarget>
|
available_targets: List<LanguageTarget>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface LanguagePickerModalSurface {
|
||||||
|
context modal: LanguagePickerModal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
modal.source_language
|
||||||
|
modal.available_targets.count
|
||||||
|
}
|
||||||
|
|
||||||
value LanguageTarget {
|
value LanguageTarget {
|
||||||
code: String
|
code: String
|
||||||
name: String
|
name: String
|
||||||
@@ -130,6 +163,16 @@ value ConfirmDeleteModal {
|
|||||||
reference_list: List<String> -- titles of referencing entities
|
reference_list: List<String> -- titles of referencing entities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ConfirmDeleteModalSurface {
|
||||||
|
context modal: ConfirmDeleteModal
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
modal.entity_name
|
||||||
|
modal.entity_type
|
||||||
|
modal.reference_count
|
||||||
|
modal.reference_list
|
||||||
|
}
|
||||||
|
|
||||||
-- ─── Confirm Dialog ──────────────────────────────────────────
|
-- ─── Confirm Dialog ──────────────────────────────────────────
|
||||||
|
|
||||||
value ConfirmDialog {
|
value ConfirmDialog {
|
||||||
@@ -141,6 +184,14 @@ value ConfirmDialog {
|
|||||||
message: String
|
message: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ConfirmDialogSurface {
|
||||||
|
context modal: ConfirmDialog
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
modal.title
|
||||||
|
modal.message
|
||||||
|
}
|
||||||
|
|
||||||
-- Native confirm dialogs (via rfd crate) are NOT modelled as values.
|
-- Native confirm dialogs (via rfd crate) are NOT modelled as values.
|
||||||
-- They are simple yes/no system dialogs with a message string.
|
-- They are simple yes/no system dialogs with a message string.
|
||||||
-- Used by: PostDelete, PostDiscard, TemplateDelete (with references).
|
-- Used by: PostDelete, PostDiscard, TemplateDelete (with references).
|
||||||
@@ -157,6 +208,14 @@ value GalleryOverlay {
|
|||||||
images: List<GalleryImage>
|
images: List<GalleryImage>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface GalleryOverlaySurface {
|
||||||
|
context overlay: GalleryOverlay
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
overlay.post_id
|
||||||
|
overlay.images.count
|
||||||
|
}
|
||||||
|
|
||||||
value GalleryImage {
|
value GalleryImage {
|
||||||
media_id: String
|
media_id: String
|
||||||
thumbnail_url: String -- bds-thumb://media_id
|
thumbnail_url: String -- bds-thumb://media_id
|
||||||
@@ -176,6 +235,17 @@ value LightboxView {
|
|||||||
alt_text: String?
|
alt_text: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface LightboxViewSurface {
|
||||||
|
context view: LightboxView
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
view.current_index
|
||||||
|
view.total_count
|
||||||
|
view.media_id
|
||||||
|
view.image_url
|
||||||
|
view.alt_text when view.alt_text != null
|
||||||
|
}
|
||||||
|
|
||||||
-- All modals rendered as centered overlay with backdrop dimming.
|
-- All modals rendered as centered overlay with backdrop dimming.
|
||||||
-- ESC key or backdrop click closes modal (cancel semantics).
|
-- ESC key or backdrop click closes modal (cancel semantics).
|
||||||
-- Overlays (PostPicker, ColourPicker) are positioned inline near trigger.
|
-- Overlays (PostPicker, ColourPicker) are positioned inline near trigger.
|
||||||
|
|||||||
@@ -3,6 +3,15 @@
|
|||||||
-- Scope: core (Wave 1)
|
-- Scope: core (Wave 1)
|
||||||
-- Distilled from: src/main/engine/ProjectEngine.ts, schema.ts
|
-- Distilled from: src/main/engine/ProjectEngine.ts, schema.ts
|
||||||
|
|
||||||
|
surface ProjectControlSurface {
|
||||||
|
facing _: ProjectOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
CreateProjectRequested(name, data_path)
|
||||||
|
SetActiveProjectRequested(project)
|
||||||
|
DeleteProjectRequested(project)
|
||||||
|
}
|
||||||
|
|
||||||
entity Project {
|
entity Project {
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
@@ -25,6 +34,24 @@ entity Project {
|
|||||||
-- Custom data path overrides default
|
-- Custom data path overrides default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ProjectSurface {
|
||||||
|
context project: Project
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
project.name
|
||||||
|
project.slug
|
||||||
|
project.description when project.description != null
|
||||||
|
project.data_path when project.data_path != null
|
||||||
|
project.is_active
|
||||||
|
project.created_at
|
||||||
|
project.updated_at
|
||||||
|
project.posts.count
|
||||||
|
project.media.count
|
||||||
|
project.tags.count
|
||||||
|
project.internal_base_dir
|
||||||
|
project.effective_data_dir
|
||||||
|
}
|
||||||
|
|
||||||
invariant SingleActiveProject {
|
invariant SingleActiveProject {
|
||||||
-- Exactly one project is active at any time
|
-- Exactly one project is active at any time
|
||||||
let active = Projects where is_active
|
let active = Projects where is_active
|
||||||
|
|||||||
@@ -25,6 +25,26 @@ value UploadTarget {
|
|||||||
remote_dir: String
|
remote_dir: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface PublishJobSurface {
|
||||||
|
context job: PublishJob
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
job.ssh_host
|
||||||
|
job.ssh_user
|
||||||
|
job.ssh_remote_path
|
||||||
|
job.ssh_mode
|
||||||
|
job.status
|
||||||
|
}
|
||||||
|
|
||||||
|
surface UploadTargetSurface {
|
||||||
|
context target: UploadTarget
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
target.kind
|
||||||
|
target.local_dir
|
||||||
|
target.remote_dir
|
||||||
|
}
|
||||||
|
|
||||||
surface PublishingControlSurface {
|
surface PublishingControlSurface {
|
||||||
facing _: PublishOperator
|
facing _: PublishOperator
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,24 @@ entity Script {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface ScriptSurface {
|
||||||
|
context script: Script
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
script.slug
|
||||||
|
script.title
|
||||||
|
script.kind
|
||||||
|
script.entrypoint
|
||||||
|
script.enabled
|
||||||
|
script.status
|
||||||
|
script.content when script.content != null
|
||||||
|
script.version
|
||||||
|
script.file_path
|
||||||
|
script.created_at
|
||||||
|
script.updated_at
|
||||||
|
script.content_location
|
||||||
|
}
|
||||||
|
|
||||||
surface ScriptManagementSurface {
|
surface ScriptManagementSurface {
|
||||||
facing _: ScriptOperator
|
facing _: ScriptOperator
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,22 @@
|
|||||||
use "./post.allium" as post
|
use "./post.allium" as post
|
||||||
use "./media.allium" as media
|
use "./media.allium" as media
|
||||||
|
|
||||||
|
surface SearchControlSurface {
|
||||||
|
facing _: SearchOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
SearchPostsRequested(query, filters)
|
||||||
|
SearchMediaRequested(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface SearchIndexRuntimeSurface {
|
||||||
|
facing _: SearchRuntime
|
||||||
|
|
||||||
|
provides:
|
||||||
|
SearchIndexUpdated(post)
|
||||||
|
SearchIndexUpdated(media)
|
||||||
|
}
|
||||||
|
|
||||||
value StemmerLanguage {
|
value StemmerLanguage {
|
||||||
-- Snowball stemmers for 24 languages
|
-- Snowball stemmers for 24 languages
|
||||||
-- ISO 639-1 to Snowball mapping
|
-- ISO 639-1 to Snowball mapping
|
||||||
@@ -14,6 +30,13 @@ value StemmerLanguage {
|
|||||||
code: String
|
code: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface StemmerLanguageSurface {
|
||||||
|
context language: StemmerLanguage
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
language.code
|
||||||
|
}
|
||||||
|
|
||||||
entity PostSearchIndex {
|
entity PostSearchIndex {
|
||||||
-- SQLite FTS5 virtual table
|
-- SQLite FTS5 virtual table
|
||||||
-- Indexed fields: title, excerpt, content, tags, categories
|
-- Indexed fields: title, excerpt, content, tags, categories
|
||||||
|
|||||||
@@ -6,6 +6,18 @@
|
|||||||
use "./project.allium" as project
|
use "./project.allium" as project
|
||||||
use "./post.allium" as post
|
use "./post.allium" as post
|
||||||
|
|
||||||
|
surface TagControlSurface {
|
||||||
|
facing _: TagOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
CreateTagRequested(project, name, color)
|
||||||
|
UpdateTagRequested(tag, changes)
|
||||||
|
DeleteTagRequested(tag)
|
||||||
|
RenameTagRequested(tag, new_name)
|
||||||
|
MergeTagsRequested(sources, target)
|
||||||
|
SyncTagsFromPostsRequested(project)
|
||||||
|
}
|
||||||
|
|
||||||
entity Tag {
|
entity Tag {
|
||||||
project: project/Project
|
project: project/Project
|
||||||
name: String
|
name: String
|
||||||
@@ -19,6 +31,20 @@ entity Tag {
|
|||||||
post_count: posts.count
|
post_count: posts.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface TagSurface {
|
||||||
|
context tag: Tag
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
tag.project
|
||||||
|
tag.name
|
||||||
|
tag.color when tag.color != null
|
||||||
|
tag.post_template_slug when tag.post_template_slug != null
|
||||||
|
tag.created_at
|
||||||
|
tag.updated_at
|
||||||
|
tag.posts.count
|
||||||
|
tag.post_count
|
||||||
|
}
|
||||||
|
|
||||||
invariant UniqueTagNamePerProject {
|
invariant UniqueTagNamePerProject {
|
||||||
-- Case-insensitive uniqueness
|
-- Case-insensitive uniqueness
|
||||||
for a in Tags:
|
for a in Tags:
|
||||||
|
|||||||
@@ -20,6 +20,37 @@ entity Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface TaskControlSurface {
|
||||||
|
facing _: TaskOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
SubmitTaskRequested(name, work)
|
||||||
|
CancelTaskRequested(task)
|
||||||
|
RegisterExternalTaskRequested(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface TaskRuntimeSurface {
|
||||||
|
facing _: TaskRuntime
|
||||||
|
|
||||||
|
provides:
|
||||||
|
TaskWorkCompleted(task)
|
||||||
|
TaskWorkFailed(task, error_message)
|
||||||
|
ProgressReported(task, value, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface TaskSurface {
|
||||||
|
context task: Task
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
task.name
|
||||||
|
task.status
|
||||||
|
task.progress when task.progress != null
|
||||||
|
task.message when task.message != null
|
||||||
|
task.group_id when task.group_id != null
|
||||||
|
task.group_name when task.group_name != null
|
||||||
|
task.created_at
|
||||||
|
}
|
||||||
|
|
||||||
config {
|
config {
|
||||||
max_concurrent: Integer = 3
|
max_concurrent: Integer = 3
|
||||||
progress_throttle: Duration = 250.milliseconds
|
progress_throttle: Duration = 250.milliseconds
|
||||||
@@ -39,9 +70,11 @@ invariant FifoQueue {
|
|||||||
rule SubmitTask {
|
rule SubmitTask {
|
||||||
when: SubmitTaskRequested(name, work)
|
when: SubmitTaskRequested(name, work)
|
||||||
let running_tasks = Tasks where status = running
|
let running_tasks = Tasks where status = running
|
||||||
ensures: Task.created(name: name, status: pending)
|
|
||||||
ensures:
|
ensures:
|
||||||
|
let task = Task.created(name: name, status: pending)
|
||||||
|
task.status = pending
|
||||||
if running_tasks.count < config.max_concurrent:
|
if running_tasks.count < config.max_concurrent:
|
||||||
|
task.status = running
|
||||||
TaskStarted(task, work)
|
TaskStarted(task, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +115,9 @@ invariant ProgressThrottled {
|
|||||||
-- External tasks: lifecycle controlled by caller (e.g., renderer-side scripts)
|
-- External tasks: lifecycle controlled by caller (e.g., renderer-side scripts)
|
||||||
rule RegisterExternalTask {
|
rule RegisterExternalTask {
|
||||||
when: RegisterExternalTaskRequested(name)
|
when: RegisterExternalTaskRequested(name)
|
||||||
ensures: Task.created(name: name, status: running)
|
ensures:
|
||||||
|
let task = Task.created(name: name, status: running)
|
||||||
|
task.status = running
|
||||||
@guidance
|
@guidance
|
||||||
-- External tasks are not managed by the queue
|
-- External tasks are not managed by the queue
|
||||||
-- The caller is responsible for updating status
|
-- The caller is responsible for updating status
|
||||||
|
|||||||
@@ -7,11 +7,35 @@
|
|||||||
use "./post.allium" as post
|
use "./post.allium" as post
|
||||||
use "./media.allium" as media
|
use "./media.allium" as media
|
||||||
|
|
||||||
|
surface TranslationControlSurface {
|
||||||
|
facing _: TranslationOperator
|
||||||
|
|
||||||
|
provides:
|
||||||
|
UpsertPostTranslationRequested(post, language, title, content, excerpt)
|
||||||
|
DeletePostTranslationRequested(translation)
|
||||||
|
ValidateTranslationsRequested(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface TranslationRuntimeSurface {
|
||||||
|
facing _: TranslationRuntime
|
||||||
|
|
||||||
|
provides:
|
||||||
|
PostPublished(post)
|
||||||
|
PublishTranslationRequested(translation)
|
||||||
|
}
|
||||||
|
|
||||||
value SupportedLanguage {
|
value SupportedLanguage {
|
||||||
-- en, de, fr, it, es
|
-- en, de, fr, it, es
|
||||||
code: String
|
code: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface SupportedLanguageSurface {
|
||||||
|
context language: SupportedLanguage
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
language.code
|
||||||
|
}
|
||||||
|
|
||||||
entity PostTranslation {
|
entity PostTranslation {
|
||||||
canonical_post: post/Post
|
canonical_post: post/Post
|
||||||
language: SupportedLanguage
|
language: SupportedLanguage
|
||||||
@@ -34,6 +58,24 @@ entity PostTranslation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surface PostTranslationSurface {
|
||||||
|
context translation: PostTranslation
|
||||||
|
|
||||||
|
exposes:
|
||||||
|
translation.canonical_post
|
||||||
|
translation.language.code
|
||||||
|
translation.title
|
||||||
|
translation.excerpt when translation.excerpt != null
|
||||||
|
translation.content when translation.content != null
|
||||||
|
translation.status
|
||||||
|
translation.file_path
|
||||||
|
translation.checksum when translation.checksum != null
|
||||||
|
translation.created_at
|
||||||
|
translation.updated_at
|
||||||
|
translation.published_at when translation.published_at != null
|
||||||
|
translation.content_location
|
||||||
|
}
|
||||||
|
|
||||||
invariant UniqueTranslationPerLanguage {
|
invariant UniqueTranslationPerLanguage {
|
||||||
for a in PostTranslations:
|
for a in PostTranslations:
|
||||||
for b in PostTranslations:
|
for b in PostTranslations:
|
||||||
@@ -54,15 +96,17 @@ invariant TranslationFilePath {
|
|||||||
rule UpsertPostTranslation {
|
rule UpsertPostTranslation {
|
||||||
when: UpsertPostTranslationRequested(post, language, title, content, excerpt)
|
when: UpsertPostTranslationRequested(post, language, title, content, excerpt)
|
||||||
requires: not post.do_not_translate
|
requires: not post.do_not_translate
|
||||||
ensures: PostTranslation.created(
|
ensures:
|
||||||
canonical_post: post,
|
let translation = PostTranslation.created(
|
||||||
language: language,
|
canonical_post: post,
|
||||||
title: title,
|
language: language,
|
||||||
content: content,
|
title: title,
|
||||||
excerpt: excerpt,
|
content: content,
|
||||||
status: draft,
|
excerpt: excerpt,
|
||||||
file_path: ""
|
status: draft,
|
||||||
)
|
file_path: ""
|
||||||
|
)
|
||||||
|
translation.status = draft
|
||||||
-- If translation already exists, update it instead
|
-- If translation already exists, update it instead
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +114,16 @@ rule PublishPostTranslation {
|
|||||||
when: PostPublished(post)
|
when: PostPublished(post)
|
||||||
-- All translations are also published when the canonical post is published
|
-- All translations are also published when the canonical post is published
|
||||||
for t in post.translations:
|
for t in post.translations:
|
||||||
ensures: t.status = published
|
ensures: PublishTranslationRequested(t)
|
||||||
ensures: t.published_at = t.published_at ?? now
|
}
|
||||||
ensures: TranslationFileWritten(t)
|
|
||||||
ensures: t.content = null
|
rule PublishTranslation {
|
||||||
-- Content moves to filesystem
|
when: PublishTranslationRequested(translation)
|
||||||
|
ensures: translation.status = published
|
||||||
|
ensures: translation.published_at = translation.published_at ?? now
|
||||||
|
ensures: TranslationFileWritten(translation)
|
||||||
|
ensures: translation.content = null
|
||||||
|
-- Content moves to filesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
rule DeletePostTranslation {
|
rule DeletePostTranslation {
|
||||||
|
|||||||
Reference in New Issue
Block a user