diff --git a/public/contracts/artifacts/contracts/mdbase.comment/1.0.0.md b/public/contracts/artifacts/contracts/mdbase.comment/1.0.0.md new file mode 100644 index 0000000..fa7df1e --- /dev/null +++ b/public/contracts/artifacts/contracts/mdbase.comment/1.0.0.md @@ -0,0 +1,73 @@ +--- +kind: mdbase.contract +contract_type: record +id: mdbase.comment +version: 1.0.0 +name: Comment +description: Comments, replies and suggested edits anchored to text in Markdown records. +record_schema: + dialect: json-schema-2020-12 + ref: ../../schemas/mdbase.comment/1.0.0.schema.json +--- + +# Comment + +A comment is its own record, never markup inside the commented record. The +commented record's Markdown stays exactly as its author wrote it, and comments +can be queried, linked to and validated like any other record. The comment's +Markdown body is its text; a link to a person in that text is a mention. + +## Threads + +The first comment of a thread has no `in_reply_to`. It carries the thread's +anchor (`target`) and state (`status`, `resolved_by`, `resolved_at`). Each reply +is a separate record whose `in_reply_to` links to that first comment, so two +people replying at once write two records and never conflict. Replies repeat +the thread's `document`. Order a thread's replies by `created_at`. + +Refer to records only with ordinary mdbase links: `document` to the commented +record, `in_reply_to` to the first comment, `created_by` and `resolved_by` to +records implementing `mdbase.person`. Implementing types declare these fields in +`collection.links` so rename reference updates keep them current. Never refer +to an author by name, email or account subject. + +## Anchors + +`target.quote` is the source of truth: `exact` is the anchored text and +`prefix` and `suffix` are the text around it. `target.text_position` records +where the quote was in one revision of the body, in Unicode code points of the +record's Markdown body as stored, after its frontmatter (profile +`markdown-body`). `basis.hash` is `sha256:` and the hex SHA-256 of that body's +UTF-8 bytes. + +To find a thread's text: + +1. When the body at the recorded offsets is still `exact`, use them. +2. Otherwise search the body for `exact`, preferring the occurrence whose + surrounding text best matches `prefix` and `suffix`, then the one nearest + the recorded offsets. +3. When `exact` is not found, the thread is detached. Show it apart from the + text rather than guessing a place. A detached thread is not an error. + +An empty `exact` is an insertion point between `prefix` and `suffix`. A thread +without a `target` is about the whole record. A consumer may rewrite +`text_position` after finding the quote elsewhere; it must not change `quote`. + +## Suggested edits + +A comment with `motivation: editing` suggests replacing the target's quote with +`suggestion.replacement`; an empty replacement suggests deleting it. Accepting +a suggestion first finds the quote as above and must refuse, leaving the +thread open, when the quote cannot be found exactly. It then replaces the +quote in the commented record and resolves the thread with +`suggestion.outcome: accepted`. Rejecting resolves it with `rejected`. + +## Lifecycle + +A comment whose `document` no longer resolves is kept, not deleted: records +can be restored, and the discussion is still collection data. Consumers may +hide such comments. Withdrawing a comment sets `deleted_at` and empties its +body rather than deleting the record, so replies to it keep their thread. + +Authorship links are editable collection data with the same trust as any other +field. They never prove who wrote a comment or grant anyone access. diff --git a/public/contracts/artifacts/contracts/mdbase.view/1.0.0.md b/public/contracts/artifacts/contracts/mdbase.view/1.0.0.md new file mode 100644 index 0000000..6c014f3 --- /dev/null +++ b/public/contracts/artifacts/contracts/mdbase.view/1.0.0.md @@ -0,0 +1,26 @@ +--- +kind: mdbase.contract +contract_type: record +id: mdbase.view +version: 1.0.0 +name: Saved view +description: Shared query scope and stable named views executable through the Query profile. +record_schema: + dialect: json-schema-2020-12 + ref: ../../schemas/mdbase.view/1.0.0.schema.json +--- + +# Saved view + +A record exposed through `mdbase.view` stores shared query scope and one or more +stable named views. Each named view resolves to the query model in Chapter 11. +Optional `presentation` metadata is advisory and does not alter headless query +results. + +Implementing this contract is how a collection declares which of its records +are saved views. A tool that advertises `view_records` discovers and executes +views through this contract's type implementations, not through a reserved type +name, path, or frontmatter value. + +This artifact is passive. Implementing it grants no authority to read, execute, +or modify any record. diff --git a/public/contracts/artifacts/contracts/obsidian.base/1.0.0.md b/public/contracts/artifacts/contracts/obsidian.base/1.0.0.md new file mode 100644 index 0000000..29fd74e --- /dev/null +++ b/public/contracts/artifacts/contracts/obsidian.base/1.0.0.md @@ -0,0 +1,36 @@ +--- +kind: mdbase.contract +contract_type: record +id: obsidian.base +version: 1.0.0 +name: Obsidian Base +description: An Obsidian Bases saved-view source stored as a YAML document record. +record_schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + type: object + required: [views] + properties: + filters: {} + formulas: { type: object } + properties: { type: object } + views: + type: array + minItems: 1 + items: { type: object, required: [type], properties: { type: { type: string }, name: { type: string } } } +--- + +# Obsidian Base + +A record exposed through `obsidian.base` is an Obsidian Bases source: global +filters, formulas, property metadata, and one or more views. The schema is +deliberately permissive. Obsidian owns the format and adds keys over time; +unknown keys are preserved and ignored. + +Tools that advertise `obsidian_bases_views` discover and execute these records +through this contract and evaluate them with the Obsidian Bases expression +dialect described in the [Obsidian Bases adapter](https://mdbase.dev/spec/adapters/obsidian-bases). + +This artifact is passive. Implementing it grants no authority to read, execute, +or modify any record. diff --git a/public/contracts/artifacts/schemas/mdbase.comment/1.0.0.schema.json b/public/contracts/artifacts/schemas/mdbase.comment/1.0.0.schema.json new file mode 100644 index 0000000..ae810d6 --- /dev/null +++ b/public/contracts/artifacts/schemas/mdbase.comment/1.0.0.schema.json @@ -0,0 +1,229 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mdbase.dev/contracts/schemas/mdbase.comment/1.0.0.schema.json", + "title": "Comment", + "description": "A comment, reply or suggested edit on a Markdown record, stored as its own record. The comment's Markdown body is its text.", + "type": "object", + "required": [ + "document", + "created_at" + ], + "properties": { + "document": { + "type": "string", + "pattern": "\\S", + "description": "Link to the commented record, such as [[chapters/method]]. Replies repeat their thread's document so one query finds a record's whole discussion." + }, + "in_reply_to": { + "type": "string", + "pattern": "\\S", + "description": "Link to the first comment of the thread this comment replies to. Absent on the first comment of a thread." + }, + "motivation": { + "enum": [ + "commenting", + "replying", + "editing" + ], + "default": "commenting", + "description": "commenting starts a thread, replying answers one, and editing suggests replacing the target's text." + }, + "target": { + "type": "object", + "description": "Where in the document the thread is anchored. Absent means the whole record. Only the first comment of a thread has a target.", + "properties": { + "quote": { + "type": "object", + "description": "The anchored text and the text around it. The quote is authoritative; positions are a hint.", + "required": [ + "exact" + ], + "properties": { + "exact": { + "type": "string", + "description": "The anchored text exactly as it appears in the record body. Empty for an insertion point, which prefix or suffix then locates." + }, + "prefix": { + "type": "string", + "description": "Text immediately before exact, to tell repeated occurrences apart." + }, + "suffix": { + "type": "string", + "description": "Text immediately after exact, to tell repeated occurrences apart." + } + }, + "if": { + "required": [ + "exact" + ], + "properties": { + "exact": { + "type": "string", + "maxLength": 0 + } + } + }, + "then": { + "anyOf": [ + { + "required": [ + "prefix" + ], + "properties": { + "prefix": { + "type": "string", + "minLength": 1 + } + } + }, + { + "required": [ + "suffix" + ], + "properties": { + "suffix": { + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "additionalProperties": false + }, + "text_position": { + "type": "object", + "description": "Offsets of the quote in one revision of the record body.", + "required": [ + "basis", + "unit", + "start", + "end" + ], + "properties": { + "basis": { + "type": "object", + "required": [ + "profile", + "hash" + ], + "properties": { + "profile": { + "const": "markdown-body", + "description": "Offsets count the record's Markdown body as stored, after the frontmatter, so frontmatter edits never move them." + }, + "hash": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$", + "description": "SHA-256 of the UTF-8 body the offsets were measured in." + } + }, + "additionalProperties": false + }, + "unit": { + "const": "unicode_code_point" + }, + "start": { + "type": "integer", + "minimum": 0 + }, + "end": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": [ + "quote" + ], + "additionalProperties": false + }, + "suggestion": { + "type": "object", + "description": "The suggested edit of an editing comment: replace the target's quote with replacement.", + "required": [ + "replacement" + ], + "properties": { + "replacement": { + "type": "string", + "description": "The text to put in place of the quote. Empty suggests deleting it." + }, + "outcome": { + "enum": [ + "accepted", + "rejected" + ], + "description": "What happened to the suggestion once its thread was resolved." + } + }, + "additionalProperties": false + }, + "status": { + "enum": [ + "open", + "resolved" + ], + "default": "open", + "description": "Whether the thread is still open. Only meaningful on the first comment of a thread." + }, + "resolved_by": { + "type": "string", + "pattern": "\\S", + "description": "Link to the mdbase.person record of who resolved the thread." + }, + "resolved_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string", + "pattern": "\\S", + "description": "Link to the mdbase.person record of the comment's author. Absent when the author has no person record." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "modified_at": { + "type": "string", + "format": "date-time" + }, + "deleted_at": { + "type": "string", + "format": "date-time", + "description": "When the comment was withdrawn. The record stays so its thread and replies keep their shape; its body should be emptied." + } + }, + "allOf": [ + { + "if": { + "properties": { + "motivation": { + "const": "editing" + } + }, + "required": [ + "motivation" + ] + }, + "then": { + "required": [ + "target", + "suggestion" + ], + "properties": { + "target": { + "type": "object" + }, + "suggestion": { + "type": "object" + } + } + } + } + ], + "additionalProperties": false +} diff --git a/public/contracts/artifacts/schemas/mdbase.view/1.0.0.schema.json b/public/contracts/artifacts/schemas/mdbase.view/1.0.0.schema.json new file mode 100644 index 0000000..d98e82f --- /dev/null +++ b/public/contracts/artifacts/schemas/mdbase.view/1.0.0.schema.json @@ -0,0 +1,261 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mdbase.dev/schemas/v0.3/view.schema.json", + "title": "mdbase.view 1.0.0 record contract view", + "description": "The contract view of a saved-view record. A type implements mdbase.view by mapping these fields; the optional type property only lets the canonical view type validate its own membership value.", + "type": "object", + "required": ["id", "version", "name", "views"], + "properties": { + "type": { "type": "string", "minLength": 1 }, + "id": { "$ref": "#/$defs/identifier" }, + "version": { "type": "integer", "minimum": 1 }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "query": { "$ref": "#/$defs/sharedQuery" }, + "properties": { "$ref": "#/$defs/propertyMetadataSet" }, + "summary_functions": { "$ref": "#/$defs/summaryFunctionSet" }, + "views": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/view" } + } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false, + "$defs": { + "identifier": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9._:-]*$" + }, + "typeName": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,127}$" + }, + "fieldName": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_:-]*$" + }, + "expression": { + "type": "string", + "minLength": 1 + }, + "extension": { + "type": "object", + "additionalProperties": true + }, + "typeList": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/typeName" } + }, + "sharedQuery": { + "type": "object", + "properties": { + "types": { "$ref": "#/$defs/typeList" }, + "where": { "$ref": "#/$defs/expression" }, + "context": { "$ref": "#/$defs/viewContext" }, + "projections": { "$ref": "#/$defs/projectionSet" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "viewContext": { + "type": "object", + "required": ["this"], + "properties": { + "this": { "$ref": "#/$defs/thisContext" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "thisContext": { + "type": "object", + "properties": { + "on_missing": { + "enum": ["view", "null", "error"], + "default": "view" + }, + "types": { "$ref": "#/$defs/typeList" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "projectionSet": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/fieldName" }, + "additionalProperties": { "$ref": "#/$defs/projection" } + }, + "projection": { + "type": "object", + "required": ["expr"], + "properties": { + "expr": { "$ref": "#/$defs/expression" }, + "description": { "type": "string" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "propertyMetadataSet": { + "type": "object", + "propertyNames": { "type": "string", "minLength": 1 }, + "additionalProperties": { "$ref": "#/$defs/propertyMetadata" } + }, + "propertyMetadata": { + "type": "object", + "properties": { + "label": { "type": "string" }, + "description": { "type": "string" }, + "format": { "type": "string" }, + "hidden": { "type": "boolean" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "summaryFunctionSet": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/fieldName" }, + "additionalProperties": { "$ref": "#/$defs/summaryFunction" } + }, + "summaryFunction": { + "type": "object", + "required": ["expr"], + "properties": { + "expr": { "$ref": "#/$defs/expression" }, + "description": { "type": "string" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "view": { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "$ref": "#/$defs/identifier" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "types": { "$ref": "#/$defs/typeList" }, + "where": { "$ref": "#/$defs/expression" }, + "context": { "$ref": "#/$defs/viewContext" }, + "projections": { "$ref": "#/$defs/projectionSet" }, + "select": { "$ref": "#/$defs/select" }, + "order_by": { "$ref": "#/$defs/orderBy" }, + "group_by": { "$ref": "#/$defs/groupBy" }, + "summaries": { "$ref": "#/$defs/summaries" }, + "limit": { "type": "integer", "minimum": 0 }, + "offset": { "type": "integer", "minimum": 0 }, + "include_body": { "type": "boolean", "default": false }, + "frontmatter_mode": { + "enum": ["effective", "persisted", "both"], + "default": "effective" + }, + "presentation": { "$ref": "#/$defs/presentation" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "select": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { "type": "string", "minLength": 1 }, + { "$ref": "#/$defs/selectExpression" } + ] + } + }, + "selectExpression": { + "type": "object", + "required": ["name", "expr"], + "properties": { + "name": { "$ref": "#/$defs/fieldName" }, + "expr": { "$ref": "#/$defs/expression" }, + "label": { "type": "string" }, + "description": { "type": "string" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "orderBy": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["field"], + "properties": { + "field": { "type": "string", "minLength": 1 }, + "direction": { "enum": ["asc", "desc"], "default": "asc" } + }, + "additionalProperties": false + } + }, + "groupBy": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["field"], + "properties": { + "field": { "type": "string", "minLength": 1 }, + "direction": { "enum": ["asc", "desc"], "default": "asc" } + }, + "additionalProperties": false + } + }, + "summaries": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/summary" } + }, + "summary": { + "type": "object", + "required": ["field", "function"], + "properties": { + "field": { "type": "string", "minLength": 1 }, + "function": { "$ref": "#/$defs/identifier" }, + "name": { "$ref": "#/$defs/fieldName" }, + "label": { "type": "string" } + }, + "additionalProperties": false + }, + "presentation": { + "type": "object", + "required": ["type"], + "properties": { + "type": { "$ref": "#/$defs/identifier" }, + "fallback": { "$ref": "#/$defs/identifier" }, + "mappings": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/fieldName" }, + "additionalProperties": { "type": "string", "minLength": 1 } + }, + "options": { + "type": "object", + "additionalProperties": true + } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + } + } +} diff --git a/public/contracts/artifacts/types/comment/1.md b/public/contracts/artifacts/types/comment/1.md new file mode 100644 index 0000000..fc439af --- /dev/null +++ b/public/contracts/artifacts/types/comment/1.md @@ -0,0 +1,235 @@ +--- +kind: mdbase.type +name: comment +version: 1 +description: A comment, reply or suggested edit on another note, anchored to a passage of its text. +match: + where: + type: comment +schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + title: Comment + description: A comment, reply or suggested edit on a Markdown record, stored as its own record. The comment's Markdown body is its text. + type: object + required: + - type + - document + - created_at + properties: + type: + const: comment + description: Identifies this note as a Comment. Set by the application that creates it. + document: + type: string + pattern: \S + description: Link to the commented record, such as [[chapters/method]]. Replies repeat their thread's document so one query finds a record's whole discussion. + in_reply_to: + type: string + pattern: \S + description: Link to the first comment of the thread this comment replies to. Absent on the first comment of a thread. + motivation: + enum: + - commenting + - replying + - editing + default: commenting + description: commenting starts a thread, replying answers one, and editing suggests replacing the target's text. + target: + type: object + description: Where in the document the thread is anchored. Absent means the whole record. Only the first comment of a thread has a target. + properties: + quote: + type: object + description: The anchored text and the text around it. The quote is authoritative; positions are a hint. + required: + - exact + properties: + exact: + type: string + description: The anchored text exactly as it appears in the record body. Empty for an insertion point, which prefix or suffix then locates. + prefix: + type: string + description: Text immediately before exact, to tell repeated occurrences apart. + suffix: + type: string + description: Text immediately after exact, to tell repeated occurrences apart. + if: + required: + - exact + properties: + exact: + type: string + maxLength: 0 + then: + anyOf: + - required: + - prefix + properties: + prefix: + type: string + minLength: 1 + - required: + - suffix + properties: + suffix: + type: string + minLength: 1 + additionalProperties: false + text_position: + type: object + description: Offsets of the quote in one revision of the record body. + required: + - basis + - unit + - start + - end + properties: + basis: + type: object + required: + - profile + - hash + properties: + profile: + const: markdown-body + description: Offsets count the record's Markdown body as stored, after the frontmatter, so frontmatter edits never move them. + hash: + type: string + pattern: ^sha256:[0-9a-f]{64}$ + description: SHA-256 of the UTF-8 body the offsets were measured in. + additionalProperties: false + unit: + const: unicode_code_point + start: + type: integer + minimum: 0 + end: + type: integer + minimum: 0 + additionalProperties: false + required: + - quote + additionalProperties: false + suggestion: + type: object + description: "The suggested edit of an editing comment: replace the target's quote with replacement." + required: + - replacement + properties: + replacement: + type: string + description: The text to put in place of the quote. Empty suggests deleting it. + outcome: + enum: + - accepted + - rejected + description: What happened to the suggestion once its thread was resolved. + additionalProperties: false + status: + enum: + - open + - resolved + default: open + description: Whether the thread is still open. Only meaningful on the first comment of a thread. + resolved_by: + type: string + pattern: \S + description: Link to the mdbase.person record of who resolved the thread. + resolved_at: + type: string + format: date-time + created_by: + type: string + pattern: \S + description: Link to the mdbase.person record of the comment's author. Absent when the author has no person record. + created_at: + type: string + format: date-time + modified_at: + type: string + format: date-time + deleted_at: + type: string + format: date-time + description: When the comment was withdrawn. The record stays so its thread and replies keep their shape; its body should be emptied. + allOf: + - if: + properties: + motivation: + const: editing + required: + - motivation + then: + required: + - target + - suggestion + properties: + target: + type: object + suggestion: + type: object + additionalProperties: true +collection: + links: + document: + target_type: any + validate_exists: false + in_reply_to: + target_type: comment + validate_exists: false + created_by: + target_type: any + validate_exists: false + resolved_by: + target_type: any + validate_exists: false + display: + description_field: document + icon: chat-circle +implements: + - contract: mdbase.comment + version: 1.0.0 + fields: + document: document + in_reply_to: in_reply_to + motivation: motivation + target: target + suggestion: suggestion + status: status + resolved_by: resolved_by + resolved_at: resolved_at + created_by: created_by + created_at: created_at + modified_at: modified_at + deleted_at: deleted_at +--- + +# Comment + +One note per comment, reply or suggested edit. Apps create these for you; the +commented note itself is never changed by commenting on it. + +```yaml +type: comment +document: "[[chapters/method]]" +target: + quote: { exact: "suggests strongly", prefix: "the evidence ", suffix: " that" } +created_by: "[[Alex Rivera]]" +created_at: 2026-09-29T10:00:00Z +``` + +The note's body is the comment's text. A reply is another Comment note whose +`in_reply_to` links to the thread's first comment. A suggested edit has +`motivation: editing` and a `suggestion.replacement`. + +`document`, `in_reply_to`, `created_by` and `resolved_by` are links, so +renaming or moving a note with a tool that updates references keeps them +current. Links to a note that has since been deleted are kept, not treated as +errors. `created_by` and `resolved_by` link to Person notes; they are +editable data, not proof of who wrote a comment. + +This type belongs to your collection once installed. You can add fields, or +rename fields and update the `implements` mapping; apps read comments through +the `mdbase.comment` contract, not through these local names. diff --git a/public/contracts/artifacts/types/obsidian-base/1.md b/public/contracts/artifacts/types/obsidian-base/1.md new file mode 100644 index 0000000..5f11af6 --- /dev/null +++ b/public/contracts/artifacts/types/obsidian-base/1.md @@ -0,0 +1,51 @@ +--- +kind: mdbase.type +name: obsidian_base +version: 1 +description: Obsidian Bases saved views stored as records +match: + path_glob: '**/*.base' +schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + title: Obsidian Base + description: An Obsidian Bases file. Obsidian owns the format; unknown keys are kept. + type: object + required: + - views + properties: + filters: {} + formulas: + type: object + properties: + type: object + views: + type: array + minItems: 1 + items: + type: object + required: + - type + properties: + type: + type: string + name: + type: string +implements: + - contract: obsidian.base + version: 1.0.0 + fields: + filters: filters + formulas: formulas + properties: properties + views: views +--- + +# Obsidian Base + +Makes `.base` files discoverable as saved views through the `obsidian.base` +contract. It takes effect when the collection lists `base` in +`settings.record_extensions`; narrow the `path_glob` to the folders that hold +your Bases. Edit Bases with whole-document replacement so Obsidian's comments +and layout survive. diff --git a/public/contracts/artifacts/types/view/1.md b/public/contracts/artifacts/types/view/1.md new file mode 100644 index 0000000..9f38f2a --- /dev/null +++ b/public/contracts/artifacts/types/view/1.md @@ -0,0 +1,352 @@ +--- +kind: mdbase.type +name: view +version: 1 +description: 'Saved views: shared query scope and stable named views' +match: + where: + type: view +schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + title: View + description: 'Saved views: shared query scope and stable named views.' + type: object + required: + - type + - id + - version + - name + - views + properties: + type: + const: view + id: + $ref: '#/$defs/identifier' + version: + type: integer + minimum: 1 + name: + type: string + minLength: 1 + description: + type: string + query: + $ref: '#/$defs/sharedQuery' + properties: + $ref: '#/$defs/propertyMetadataSet' + summary_functions: + $ref: '#/$defs/summaryFunctionSet' + views: + type: array + minItems: 1 + items: + $ref: '#/$defs/view' + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + $defs: + identifier: + type: string + pattern: ^[A-Za-z][A-Za-z0-9._:-]*$ + typeName: + type: string + pattern: ^[A-Za-z][A-Za-z0-9_-]{0,127}$ + fieldName: + type: string + pattern: ^[A-Za-z_][A-Za-z0-9_:-]*$ + expression: + type: string + minLength: 1 + extension: + type: object + additionalProperties: true + typeList: + type: array + minItems: 1 + uniqueItems: true + items: + $ref: '#/$defs/typeName' + sharedQuery: + type: object + properties: + types: + $ref: '#/$defs/typeList' + where: + $ref: '#/$defs/expression' + context: + $ref: '#/$defs/viewContext' + projections: + $ref: '#/$defs/projectionSet' + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + viewContext: + type: object + required: + - this + properties: + this: + $ref: '#/$defs/thisContext' + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + thisContext: + type: object + properties: + on_missing: + enum: + - view + - 'null' + - error + default: view + types: + $ref: '#/$defs/typeList' + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + projectionSet: + type: object + propertyNames: + $ref: '#/$defs/fieldName' + additionalProperties: + $ref: '#/$defs/projection' + projection: + type: object + required: + - expr + properties: + expr: + $ref: '#/$defs/expression' + description: + type: string + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + propertyMetadataSet: + type: object + propertyNames: + type: string + minLength: 1 + additionalProperties: + $ref: '#/$defs/propertyMetadata' + propertyMetadata: + type: object + properties: + label: + type: string + description: + type: string + format: + type: string + hidden: + type: boolean + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + summaryFunctionSet: + type: object + propertyNames: + $ref: '#/$defs/fieldName' + additionalProperties: + $ref: '#/$defs/summaryFunction' + summaryFunction: + type: object + required: + - expr + properties: + expr: + $ref: '#/$defs/expression' + description: + type: string + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + view: + type: object + required: + - id + - name + properties: + id: + $ref: '#/$defs/identifier' + name: + type: string + minLength: 1 + description: + type: string + types: + $ref: '#/$defs/typeList' + where: + $ref: '#/$defs/expression' + context: + $ref: '#/$defs/viewContext' + projections: + $ref: '#/$defs/projectionSet' + select: + $ref: '#/$defs/select' + order_by: + $ref: '#/$defs/orderBy' + group_by: + $ref: '#/$defs/groupBy' + summaries: + $ref: '#/$defs/summaries' + limit: + type: integer + minimum: 0 + offset: + type: integer + minimum: 0 + include_body: + type: boolean + default: false + frontmatter_mode: + enum: + - effective + - persisted + - both + default: effective + presentation: + $ref: '#/$defs/presentation' + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + select: + type: array + minItems: 1 + items: + oneOf: + - type: string + minLength: 1 + - $ref: '#/$defs/selectExpression' + selectExpression: + type: object + required: + - name + - expr + properties: + name: + $ref: '#/$defs/fieldName' + expr: + $ref: '#/$defs/expression' + label: + type: string + description: + type: string + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false + orderBy: + type: array + minItems: 1 + items: + type: object + required: + - field + properties: + field: + type: string + minLength: 1 + direction: + enum: + - asc + - desc + default: asc + additionalProperties: false + groupBy: + type: array + minItems: 1 + items: + type: object + required: + - field + properties: + field: + type: string + minLength: 1 + direction: + enum: + - asc + - desc + default: asc + additionalProperties: false + summaries: + type: array + minItems: 1 + items: + $ref: '#/$defs/summary' + summary: + type: object + required: + - field + - function + properties: + field: + type: string + minLength: 1 + function: + $ref: '#/$defs/identifier' + name: + $ref: '#/$defs/fieldName' + label: + type: string + additionalProperties: false + presentation: + type: object + required: + - type + properties: + type: + $ref: '#/$defs/identifier' + fallback: + $ref: '#/$defs/identifier' + mappings: + type: object + propertyNames: + $ref: '#/$defs/fieldName' + additionalProperties: + type: string + minLength: 1 + options: + type: object + additionalProperties: true + patternProperties: + ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$: + $ref: '#/$defs/extension' + additionalProperties: false +implements: +- contract: mdbase.view + version: 1.0.0 + fields: + id: id + version: version + name: name + description: description + query: query + properties: properties + summary_functions: summary_functions + views: views +collection: + display: + name_field: name +--- + +# View + +A view record stores shared query scope and one or more stable named views. +Tools that support saved views discover these records through the `mdbase.view` +contract and run each named view as an ordinary query. + +This type belongs to your collection once installed. You can rename fields or +change how records are matched, as long as the `implements` mapping still +points each contract field at the right frontmatter field. diff --git a/public/contracts/catalog.json b/public/contracts/catalog.json index 443a622..174ca55 100644 --- a/public/contracts/catalog.json +++ b/public/contracts/catalog.json @@ -9,6 +9,16 @@ "url": "https://mdbase.dev/" }, "contracts": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "name": "Comment", + "description": "Comments, replies and suggested edits anchored to text in Markdown records.", + "contract_type": "record", + "digest": "sha256:c85663fbb339d33e8e67825c81fc160fbac00285a1687cbe99187bad7f58878d", + "artifact": "./artifacts/contracts/mdbase.comment/1.0.0.md", + "standards": [] + }, { "id": "mdbase.contact", "version": "1.0.0", @@ -199,6 +209,26 @@ "artifact": "./artifacts/contracts/mdbase.runtime.workflow/1.0.0.md", "standards": [] }, + { + "id": "mdbase.view", + "version": "1.0.0", + "name": "Saved view", + "description": "Shared query scope and stable named views executable through the Query profile.", + "contract_type": "record", + "digest": "sha256:91eae42371c2d411b20d3c976aece3071bbd753963b77b6bcdc26137a8fdcded", + "artifact": "./artifacts/contracts/mdbase.view/1.0.0.md", + "standards": [] + }, + { + "id": "obsidian.base", + "version": "1.0.0", + "name": "Obsidian Base", + "description": "An Obsidian Bases saved-view source stored as a YAML document record.", + "contract_type": "record", + "digest": "sha256:fca77a9c845223784d4af7f5e13e2f149295621ad1b794ea9bb4d99f604467e8", + "artifact": "./artifacts/contracts/obsidian.base/1.0.0.md", + "standards": [] + }, { "id": "tasknotes.task", "version": "0.3.0-rc.3", @@ -221,6 +251,44 @@ } ], "packs": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "name": "Comments type pack", + "description": "Comments, replies and suggested edits anchored to text in Markdown records.", + "digest": "sha256:37729ce8eca79ed52b669b191350056cbd23d2fe1453d38b56805979ce0d9e29", + "provision": "./packs/mdbase.comment/1.0.0.json", + "provides": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "digest": "sha256:c2c5b3f3013d10625310401e9f9149ffa2d09c5b7d91b4fc97688d3ac010bec5" + } + ], + "resource_count": 3, + "display": { + "name": "Comments", + "summary": "Discuss and suggest edits to notes without changing their text, in any app that reads comments.", + "category": "work", + "audience": "general", + "icon": "chat-circle", + "badges": [ + "Portable comments", + "Suggested edits" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "optional", + "primary_type": "comment", + "types": [ + { + "name": "comment", + "label": "Comment" + } + ] + } + }, { "id": "mdbase.contact", "version": "1.0.0", @@ -465,6 +533,80 @@ ] } }, + { + "id": "mdbase.view", + "version": "1.0.0", + "name": "Saved views", + "description": "The mdbase.view contract and an editable View type for saved, reusable queries.", + "digest": "sha256:d2a3ff8629699b183bb8117d7499697fecc7685ed8558cae6d9a87311d7cfd05", + "provision": "./packs/mdbase.view/1.0.0.json", + "provides": [ + { + "id": "mdbase.view", + "version": "1.0.0", + "digest": "sha256:0918acefce08cddd778fab768fe4f2e4615d263a0eec3e5330b6897c686d744e" + } + ], + "resource_count": 3, + "display": { + "name": "Saved views", + "summary": "Save reusable queries that any view-aware app can list and run.", + "category": "work", + "audience": "general", + "icon": "table", + "badges": [ + "Portable views" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "optional", + "primary_type": "view", + "types": [ + { + "name": "view", + "label": "View" + } + ] + } + }, + { + "id": "obsidian.base", + "version": "1.0.0", + "name": "Obsidian Bases", + "description": "The obsidian.base contract and an editable type that stores Obsidian Bases as records.", + "digest": "sha256:8251b351a4b00c4ea3f4d49988ae45ab52ceaf2420f57ce8105ca4cfe5eafd50", + "provision": "./packs/obsidian.base/1.0.0.json", + "provides": [ + { + "id": "obsidian.base", + "version": "1.0.0", + "digest": "sha256:37e8a0bfb32bb4795c1b3c5dec613c32429aaa765dc33e4adc0c627a48f5c1ff" + } + ], + "resource_count": 2, + "display": { + "name": "Obsidian Bases", + "summary": "Use Obsidian Bases files as saved views that any view-aware app can list and run.", + "category": "work", + "audience": "general", + "icon": "table", + "badges": [ + "Obsidian compatible" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "optional", + "primary_type": "obsidian_base", + "types": [ + { + "name": "obsidian_base", + "label": "Obsidian base" + } + ] + } + }, { "id": "tasknotes.task", "version": "0.3.0-rc.12", diff --git a/public/contracts/packs/mdbase.comment/1.0.0.json b/public/contracts/packs/mdbase.comment/1.0.0.json new file mode 100644 index 0000000..35a22ea --- /dev/null +++ b/public/contracts/packs/mdbase.comment/1.0.0.json @@ -0,0 +1,53 @@ +{ + "manifest": { + "kind": "mdbase.type-pack", + "id": "mdbase.comment", + "version": "1.0.0", + "name": "Comments type pack", + "description": "Comments, replies and suggested edits anchored to text in Markdown records.", + "resources": [ + { + "kind": "schema", + "mode": "managed", + "source": "schemas/mdbase.comment/1.0.0.schema.json", + "target": "schemas/mdbase.comment/1.0.0.schema.json", + "digest": "sha256:b845720a44e3fcea2385ef33ae2803531f2fad655efdc2c262e51f92f24f0e74" + }, + { + "kind": "contract", + "mode": "managed", + "source": "contracts/mdbase.comment/1.0.0.md", + "target": "_contracts/mdbase.comment/1.0.0.md", + "digest": "sha256:c85663fbb339d33e8e67825c81fc160fbac00285a1687cbe99187bad7f58878d" + }, + { + "kind": "type", + "mode": "seed", + "source": "types/comment/1.md", + "target": "_types/comment.md", + "digest": "sha256:e8abf287b9d3df0035fd32a2a584d7a8d21eddd7b4a3201fd32a3babe6ac9d9e" + } + ] + }, + "resources": [ + { + "source": "schemas/mdbase.comment/1.0.0.schema.json", + "document": "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://mdbase.dev/contracts/schemas/mdbase.comment/1.0.0.schema.json\",\n \"title\": \"Comment\",\n \"description\": \"A comment, reply or suggested edit on a Markdown record, stored as its own record. The comment's Markdown body is its text.\",\n \"type\": \"object\",\n \"required\": [\n \"document\",\n \"created_at\"\n ],\n \"properties\": {\n \"document\": {\n \"type\": \"string\",\n \"pattern\": \"\\\\S\",\n \"description\": \"Link to the commented record, such as [[chapters/method]]. Replies repeat their thread's document so one query finds a record's whole discussion.\"\n },\n \"in_reply_to\": {\n \"type\": \"string\",\n \"pattern\": \"\\\\S\",\n \"description\": \"Link to the first comment of the thread this comment replies to. Absent on the first comment of a thread.\"\n },\n \"motivation\": {\n \"enum\": [\n \"commenting\",\n \"replying\",\n \"editing\"\n ],\n \"default\": \"commenting\",\n \"description\": \"commenting starts a thread, replying answers one, and editing suggests replacing the target's text.\"\n },\n \"target\": {\n \"type\": \"object\",\n \"description\": \"Where in the document the thread is anchored. Absent means the whole record. Only the first comment of a thread has a target.\",\n \"properties\": {\n \"quote\": {\n \"type\": \"object\",\n \"description\": \"The anchored text and the text around it. The quote is authoritative; positions are a hint.\",\n \"required\": [\n \"exact\"\n ],\n \"properties\": {\n \"exact\": {\n \"type\": \"string\",\n \"description\": \"The anchored text exactly as it appears in the record body. Empty for an insertion point, which prefix or suffix then locates.\"\n },\n \"prefix\": {\n \"type\": \"string\",\n \"description\": \"Text immediately before exact, to tell repeated occurrences apart.\"\n },\n \"suffix\": {\n \"type\": \"string\",\n \"description\": \"Text immediately after exact, to tell repeated occurrences apart.\"\n }\n },\n \"if\": {\n \"required\": [\n \"exact\"\n ],\n \"properties\": {\n \"exact\": {\n \"type\": \"string\",\n \"maxLength\": 0\n }\n }\n },\n \"then\": {\n \"anyOf\": [\n {\n \"required\": [\n \"prefix\"\n ],\n \"properties\": {\n \"prefix\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"required\": [\n \"suffix\"\n ],\n \"properties\": {\n \"suffix\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n }\n ]\n },\n \"additionalProperties\": false\n },\n \"text_position\": {\n \"type\": \"object\",\n \"description\": \"Offsets of the quote in one revision of the record body.\",\n \"required\": [\n \"basis\",\n \"unit\",\n \"start\",\n \"end\"\n ],\n \"properties\": {\n \"basis\": {\n \"type\": \"object\",\n \"required\": [\n \"profile\",\n \"hash\"\n ],\n \"properties\": {\n \"profile\": {\n \"const\": \"markdown-body\",\n \"description\": \"Offsets count the record's Markdown body as stored, after the frontmatter, so frontmatter edits never move them.\"\n },\n \"hash\": {\n \"type\": \"string\",\n \"pattern\": \"^sha256:[0-9a-f]{64}$\",\n \"description\": \"SHA-256 of the UTF-8 body the offsets were measured in.\"\n }\n },\n \"additionalProperties\": false\n },\n \"unit\": {\n \"const\": \"unicode_code_point\"\n },\n \"start\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"end\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n },\n \"additionalProperties\": false\n }\n },\n \"required\": [\n \"quote\"\n ],\n \"additionalProperties\": false\n },\n \"suggestion\": {\n \"type\": \"object\",\n \"description\": \"The suggested edit of an editing comment: replace the target's quote with replacement.\",\n \"required\": [\n \"replacement\"\n ],\n \"properties\": {\n \"replacement\": {\n \"type\": \"string\",\n \"description\": \"The text to put in place of the quote. Empty suggests deleting it.\"\n },\n \"outcome\": {\n \"enum\": [\n \"accepted\",\n \"rejected\"\n ],\n \"description\": \"What happened to the suggestion once its thread was resolved.\"\n }\n },\n \"additionalProperties\": false\n },\n \"status\": {\n \"enum\": [\n \"open\",\n \"resolved\"\n ],\n \"default\": \"open\",\n \"description\": \"Whether the thread is still open. Only meaningful on the first comment of a thread.\"\n },\n \"resolved_by\": {\n \"type\": \"string\",\n \"pattern\": \"\\\\S\",\n \"description\": \"Link to the mdbase.person record of who resolved the thread.\"\n },\n \"resolved_at\": {\n \"type\": \"string\",\n \"format\": \"date-time\"\n },\n \"created_by\": {\n \"type\": \"string\",\n \"pattern\": \"\\\\S\",\n \"description\": \"Link to the mdbase.person record of the comment's author. Absent when the author has no person record.\"\n },\n \"created_at\": {\n \"type\": \"string\",\n \"format\": \"date-time\"\n },\n \"modified_at\": {\n \"type\": \"string\",\n \"format\": \"date-time\"\n },\n \"deleted_at\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"When the comment was withdrawn. The record stays so its thread and replies keep their shape; its body should be emptied.\"\n }\n },\n \"allOf\": [\n {\n \"if\": {\n \"properties\": {\n \"motivation\": {\n \"const\": \"editing\"\n }\n },\n \"required\": [\n \"motivation\"\n ]\n },\n \"then\": {\n \"required\": [\n \"target\",\n \"suggestion\"\n ],\n \"properties\": {\n \"target\": {\n \"type\": \"object\"\n },\n \"suggestion\": {\n \"type\": \"object\"\n }\n }\n }\n }\n ],\n \"additionalProperties\": false\n}\n" + }, + { + "source": "contracts/mdbase.comment/1.0.0.md", + "document": "---\nkind: mdbase.contract\ncontract_type: record\nid: mdbase.comment\nversion: 1.0.0\nname: Comment\ndescription: Comments, replies and suggested edits anchored to text in Markdown records.\nrecord_schema:\n dialect: json-schema-2020-12\n ref: ../../schemas/mdbase.comment/1.0.0.schema.json\n---\n\n# Comment\n\nA comment is its own record, never markup inside the commented record. The\ncommented record's Markdown stays exactly as its author wrote it, and comments\ncan be queried, linked to and validated like any other record. The comment's\nMarkdown body is its text; a link to a person in that text is a mention.\n\n## Threads\n\nThe first comment of a thread has no `in_reply_to`. It carries the thread's\nanchor (`target`) and state (`status`, `resolved_by`, `resolved_at`). Each reply\nis a separate record whose `in_reply_to` links to that first comment, so two\npeople replying at once write two records and never conflict. Replies repeat\nthe thread's `document`. Order a thread's replies by `created_at`.\n\nRefer to records only with ordinary mdbase links: `document` to the commented\nrecord, `in_reply_to` to the first comment, `created_by` and `resolved_by` to\nrecords implementing `mdbase.person`. Implementing types declare these fields in\n`collection.links` so rename reference updates keep them current. Never refer\nto an author by name, email or account subject.\n\n## Anchors\n\n`target.quote` is the source of truth: `exact` is the anchored text and\n`prefix` and `suffix` are the text around it. `target.text_position` records\nwhere the quote was in one revision of the body, in Unicode code points of the\nrecord's Markdown body as stored, after its frontmatter (profile\n`markdown-body`). `basis.hash` is `sha256:` and the hex SHA-256 of that body's\nUTF-8 bytes.\n\nTo find a thread's text:\n\n1. When the body at the recorded offsets is still `exact`, use them.\n2. Otherwise search the body for `exact`, preferring the occurrence whose\n surrounding text best matches `prefix` and `suffix`, then the one nearest\n the recorded offsets.\n3. When `exact` is not found, the thread is detached. Show it apart from the\n text rather than guessing a place. A detached thread is not an error.\n\nAn empty `exact` is an insertion point between `prefix` and `suffix`. A thread\nwithout a `target` is about the whole record. A consumer may rewrite\n`text_position` after finding the quote elsewhere; it must not change `quote`.\n\n## Suggested edits\n\nA comment with `motivation: editing` suggests replacing the target's quote with\n`suggestion.replacement`; an empty replacement suggests deleting it. Accepting\na suggestion first finds the quote as above and must refuse, leaving the\nthread open, when the quote cannot be found exactly. It then replaces the\nquote in the commented record and resolves the thread with\n`suggestion.outcome: accepted`. Rejecting resolves it with `rejected`.\n\n## Lifecycle\n\nA comment whose `document` no longer resolves is kept, not deleted: records\ncan be restored, and the discussion is still collection data. Consumers may\nhide such comments. Withdrawing a comment sets `deleted_at` and empties its\nbody rather than deleting the record, so replies to it keep their thread.\n\nAuthorship links are editable collection data with the same trust as any other\nfield. They never prove who wrote a comment or grant anyone access.\n" + }, + { + "source": "types/comment/1.md", + "document": "---\nkind: mdbase.type\nname: comment\nversion: 1\ndescription: A comment, reply or suggested edit on another note, anchored to a passage of its text.\nmatch:\n where:\n type: comment\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: https://json-schema.org/draft/2020-12/schema\n title: Comment\n description: A comment, reply or suggested edit on a Markdown record, stored as its own record. The comment's Markdown body is its text.\n type: object\n required:\n - type\n - document\n - created_at\n properties:\n type:\n const: comment\n description: Identifies this note as a Comment. Set by the application that creates it.\n document:\n type: string\n pattern: \\S\n description: Link to the commented record, such as [[chapters/method]]. Replies repeat their thread's document so one query finds a record's whole discussion.\n in_reply_to:\n type: string\n pattern: \\S\n description: Link to the first comment of the thread this comment replies to. Absent on the first comment of a thread.\n motivation:\n enum:\n - commenting\n - replying\n - editing\n default: commenting\n description: commenting starts a thread, replying answers one, and editing suggests replacing the target's text.\n target:\n type: object\n description: Where in the document the thread is anchored. Absent means the whole record. Only the first comment of a thread has a target.\n properties:\n quote:\n type: object\n description: The anchored text and the text around it. The quote is authoritative; positions are a hint.\n required:\n - exact\n properties:\n exact:\n type: string\n description: The anchored text exactly as it appears in the record body. Empty for an insertion point, which prefix or suffix then locates.\n prefix:\n type: string\n description: Text immediately before exact, to tell repeated occurrences apart.\n suffix:\n type: string\n description: Text immediately after exact, to tell repeated occurrences apart.\n if:\n required:\n - exact\n properties:\n exact:\n type: string\n maxLength: 0\n then:\n anyOf:\n - required:\n - prefix\n properties:\n prefix:\n type: string\n minLength: 1\n - required:\n - suffix\n properties:\n suffix:\n type: string\n minLength: 1\n additionalProperties: false\n text_position:\n type: object\n description: Offsets of the quote in one revision of the record body.\n required:\n - basis\n - unit\n - start\n - end\n properties:\n basis:\n type: object\n required:\n - profile\n - hash\n properties:\n profile:\n const: markdown-body\n description: Offsets count the record's Markdown body as stored, after the frontmatter, so frontmatter edits never move them.\n hash:\n type: string\n pattern: ^sha256:[0-9a-f]{64}$\n description: SHA-256 of the UTF-8 body the offsets were measured in.\n additionalProperties: false\n unit:\n const: unicode_code_point\n start:\n type: integer\n minimum: 0\n end:\n type: integer\n minimum: 0\n additionalProperties: false\n required:\n - quote\n additionalProperties: false\n suggestion:\n type: object\n description: \"The suggested edit of an editing comment: replace the target's quote with replacement.\"\n required:\n - replacement\n properties:\n replacement:\n type: string\n description: The text to put in place of the quote. Empty suggests deleting it.\n outcome:\n enum:\n - accepted\n - rejected\n description: What happened to the suggestion once its thread was resolved.\n additionalProperties: false\n status:\n enum:\n - open\n - resolved\n default: open\n description: Whether the thread is still open. Only meaningful on the first comment of a thread.\n resolved_by:\n type: string\n pattern: \\S\n description: Link to the mdbase.person record of who resolved the thread.\n resolved_at:\n type: string\n format: date-time\n created_by:\n type: string\n pattern: \\S\n description: Link to the mdbase.person record of the comment's author. Absent when the author has no person record.\n created_at:\n type: string\n format: date-time\n modified_at:\n type: string\n format: date-time\n deleted_at:\n type: string\n format: date-time\n description: When the comment was withdrawn. The record stays so its thread and replies keep their shape; its body should be emptied.\n allOf:\n - if:\n properties:\n motivation:\n const: editing\n required:\n - motivation\n then:\n required:\n - target\n - suggestion\n properties:\n target:\n type: object\n suggestion:\n type: object\n additionalProperties: true\ncollection:\n links:\n document:\n target_type: any\n validate_exists: false\n in_reply_to:\n target_type: comment\n validate_exists: false\n created_by:\n target_type: any\n validate_exists: false\n resolved_by:\n target_type: any\n validate_exists: false\n display:\n description_field: document\n icon: chat-circle\nimplements:\n - contract: mdbase.comment\n version: 1.0.0\n fields:\n document: document\n in_reply_to: in_reply_to\n motivation: motivation\n target: target\n suggestion: suggestion\n status: status\n resolved_by: resolved_by\n resolved_at: resolved_at\n created_by: created_by\n created_at: created_at\n modified_at: modified_at\n deleted_at: deleted_at\n---\n\n# Comment\n\nOne note per comment, reply or suggested edit. Apps create these for you; the\ncommented note itself is never changed by commenting on it.\n\n```yaml\ntype: comment\ndocument: \"[[chapters/method]]\"\ntarget:\n quote: { exact: \"suggests strongly\", prefix: \"the evidence \", suffix: \" that\" }\ncreated_by: \"[[Alex Rivera]]\"\ncreated_at: 2026-09-29T10:00:00Z\n```\n\nThe note's body is the comment's text. A reply is another Comment note whose\n`in_reply_to` links to the thread's first comment. A suggested edit has\n`motivation: editing` and a `suggestion.replacement`.\n\n`document`, `in_reply_to`, `created_by` and `resolved_by` are links, so\nrenaming or moving a note with a tool that updates references keeps them\ncurrent. Links to a note that has since been deleted are kept, not treated as\nerrors. `created_by` and `resolved_by` link to Person notes; they are\neditable data, not proof of who wrote a comment.\n\nThis type belongs to your collection once installed. You can add fields, or\nrename fields and update the `implements` mapping; apps read comments through\nthe `mdbase.comment` contract, not through these local names.\n" + } + ], + "provides": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "digest": "sha256:c2c5b3f3013d10625310401e9f9149ffa2d09c5b7d91b4fc97688d3ac010bec5" + } + ] +} diff --git a/public/contracts/packs/mdbase.view/1.0.0.json b/public/contracts/packs/mdbase.view/1.0.0.json new file mode 100644 index 0000000..210fa58 --- /dev/null +++ b/public/contracts/packs/mdbase.view/1.0.0.json @@ -0,0 +1,53 @@ +{ + "manifest": { + "kind": "mdbase.type-pack", + "id": "mdbase.view", + "version": "1.0.0", + "name": "Saved views", + "description": "The mdbase.view contract and an editable View type for saved, reusable queries.", + "resources": [ + { + "kind": "schema", + "mode": "managed", + "source": "schemas/mdbase.view/1.0.0.schema.json", + "target": "schemas/mdbase.view/1.0.0.schema.json", + "digest": "sha256:d58b8b691c14d59f0575fc1cc93ab5b46363c485e4469bc58039ec0cece7ca36" + }, + { + "kind": "contract", + "mode": "managed", + "source": "contracts/mdbase.view/1.0.0.md", + "target": "_contracts/mdbase.view/1.0.0.md", + "digest": "sha256:91eae42371c2d411b20d3c976aece3071bbd753963b77b6bcdc26137a8fdcded" + }, + { + "kind": "type", + "mode": "seed", + "source": "types/view/1.md", + "target": "_types/view.md", + "digest": "sha256:480919f6f88f91592abb9fe7971f09e874d536c6883981616b181cf14cbe7ff4" + } + ] + }, + "resources": [ + { + "source": "schemas/mdbase.view/1.0.0.schema.json", + "document": "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://mdbase.dev/schemas/v0.3/view.schema.json\",\n \"title\": \"mdbase.view 1.0.0 record contract view\",\n \"description\": \"The contract view of a saved-view record. A type implements mdbase.view by mapping these fields; the optional type property only lets the canonical view type validate its own membership value.\",\n \"type\": \"object\",\n \"required\": [\"id\", \"version\", \"name\", \"views\"],\n \"properties\": {\n \"type\": { \"type\": \"string\", \"minLength\": 1 },\n \"id\": { \"$ref\": \"#/$defs/identifier\" },\n \"version\": { \"type\": \"integer\", \"minimum\": 1 },\n \"name\": { \"type\": \"string\", \"minLength\": 1 },\n \"description\": { \"type\": \"string\" },\n \"query\": { \"$ref\": \"#/$defs/sharedQuery\" },\n \"properties\": { \"$ref\": \"#/$defs/propertyMetadataSet\" },\n \"summary_functions\": { \"$ref\": \"#/$defs/summaryFunctionSet\" },\n \"views\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": { \"$ref\": \"#/$defs/view\" }\n }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false,\n \"$defs\": {\n \"identifier\": {\n \"type\": \"string\",\n \"pattern\": \"^[A-Za-z][A-Za-z0-9._:-]*$\"\n },\n \"typeName\": {\n \"type\": \"string\",\n \"pattern\": \"^[A-Za-z][A-Za-z0-9_-]{0,127}$\"\n },\n \"fieldName\": {\n \"type\": \"string\",\n \"pattern\": \"^[A-Za-z_][A-Za-z0-9_:-]*$\"\n },\n \"expression\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"extension\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"typeList\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"uniqueItems\": true,\n \"items\": { \"$ref\": \"#/$defs/typeName\" }\n },\n \"sharedQuery\": {\n \"type\": \"object\",\n \"properties\": {\n \"types\": { \"$ref\": \"#/$defs/typeList\" },\n \"where\": { \"$ref\": \"#/$defs/expression\" },\n \"context\": { \"$ref\": \"#/$defs/viewContext\" },\n \"projections\": { \"$ref\": \"#/$defs/projectionSet\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"viewContext\": {\n \"type\": \"object\",\n \"required\": [\"this\"],\n \"properties\": {\n \"this\": { \"$ref\": \"#/$defs/thisContext\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"thisContext\": {\n \"type\": \"object\",\n \"properties\": {\n \"on_missing\": {\n \"enum\": [\"view\", \"null\", \"error\"],\n \"default\": \"view\"\n },\n \"types\": { \"$ref\": \"#/$defs/typeList\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"projectionSet\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/fieldName\" },\n \"additionalProperties\": { \"$ref\": \"#/$defs/projection\" }\n },\n \"projection\": {\n \"type\": \"object\",\n \"required\": [\"expr\"],\n \"properties\": {\n \"expr\": { \"$ref\": \"#/$defs/expression\" },\n \"description\": { \"type\": \"string\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"propertyMetadataSet\": {\n \"type\": \"object\",\n \"propertyNames\": { \"type\": \"string\", \"minLength\": 1 },\n \"additionalProperties\": { \"$ref\": \"#/$defs/propertyMetadata\" }\n },\n \"propertyMetadata\": {\n \"type\": \"object\",\n \"properties\": {\n \"label\": { \"type\": \"string\" },\n \"description\": { \"type\": \"string\" },\n \"format\": { \"type\": \"string\" },\n \"hidden\": { \"type\": \"boolean\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"summaryFunctionSet\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/fieldName\" },\n \"additionalProperties\": { \"$ref\": \"#/$defs/summaryFunction\" }\n },\n \"summaryFunction\": {\n \"type\": \"object\",\n \"required\": [\"expr\"],\n \"properties\": {\n \"expr\": { \"$ref\": \"#/$defs/expression\" },\n \"description\": { \"type\": \"string\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"view\": {\n \"type\": \"object\",\n \"required\": [\"id\", \"name\"],\n \"properties\": {\n \"id\": { \"$ref\": \"#/$defs/identifier\" },\n \"name\": { \"type\": \"string\", \"minLength\": 1 },\n \"description\": { \"type\": \"string\" },\n \"types\": { \"$ref\": \"#/$defs/typeList\" },\n \"where\": { \"$ref\": \"#/$defs/expression\" },\n \"context\": { \"$ref\": \"#/$defs/viewContext\" },\n \"projections\": { \"$ref\": \"#/$defs/projectionSet\" },\n \"select\": { \"$ref\": \"#/$defs/select\" },\n \"order_by\": { \"$ref\": \"#/$defs/orderBy\" },\n \"group_by\": { \"$ref\": \"#/$defs/groupBy\" },\n \"summaries\": { \"$ref\": \"#/$defs/summaries\" },\n \"limit\": { \"type\": \"integer\", \"minimum\": 0 },\n \"offset\": { \"type\": \"integer\", \"minimum\": 0 },\n \"include_body\": { \"type\": \"boolean\", \"default\": false },\n \"frontmatter_mode\": {\n \"enum\": [\"effective\", \"persisted\", \"both\"],\n \"default\": \"effective\"\n },\n \"presentation\": { \"$ref\": \"#/$defs/presentation\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"select\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"oneOf\": [\n { \"type\": \"string\", \"minLength\": 1 },\n { \"$ref\": \"#/$defs/selectExpression\" }\n ]\n }\n },\n \"selectExpression\": {\n \"type\": \"object\",\n \"required\": [\"name\", \"expr\"],\n \"properties\": {\n \"name\": { \"$ref\": \"#/$defs/fieldName\" },\n \"expr\": { \"$ref\": \"#/$defs/expression\" },\n \"label\": { \"type\": \"string\" },\n \"description\": { \"type\": \"string\" }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n },\n \"orderBy\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"field\"],\n \"properties\": {\n \"field\": { \"type\": \"string\", \"minLength\": 1 },\n \"direction\": { \"enum\": [\"asc\", \"desc\"], \"default\": \"asc\" }\n },\n \"additionalProperties\": false\n }\n },\n \"groupBy\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"field\"],\n \"properties\": {\n \"field\": { \"type\": \"string\", \"minLength\": 1 },\n \"direction\": { \"enum\": [\"asc\", \"desc\"], \"default\": \"asc\" }\n },\n \"additionalProperties\": false\n }\n },\n \"summaries\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": { \"$ref\": \"#/$defs/summary\" }\n },\n \"summary\": {\n \"type\": \"object\",\n \"required\": [\"field\", \"function\"],\n \"properties\": {\n \"field\": { \"type\": \"string\", \"minLength\": 1 },\n \"function\": { \"$ref\": \"#/$defs/identifier\" },\n \"name\": { \"$ref\": \"#/$defs/fieldName\" },\n \"label\": { \"type\": \"string\" }\n },\n \"additionalProperties\": false\n },\n \"presentation\": {\n \"type\": \"object\",\n \"required\": [\"type\"],\n \"properties\": {\n \"type\": { \"$ref\": \"#/$defs/identifier\" },\n \"fallback\": { \"$ref\": \"#/$defs/identifier\" },\n \"mappings\": {\n \"type\": \"object\",\n \"propertyNames\": { \"$ref\": \"#/$defs/fieldName\" },\n \"additionalProperties\": { \"type\": \"string\", \"minLength\": 1 }\n },\n \"options\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n }\n },\n \"patternProperties\": {\n \"^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$\": { \"$ref\": \"#/$defs/extension\" }\n },\n \"additionalProperties\": false\n }\n }\n}\n" + }, + { + "source": "contracts/mdbase.view/1.0.0.md", + "document": "---\nkind: mdbase.contract\ncontract_type: record\nid: mdbase.view\nversion: 1.0.0\nname: Saved view\ndescription: Shared query scope and stable named views executable through the Query profile.\nrecord_schema:\n dialect: json-schema-2020-12\n ref: ../../schemas/mdbase.view/1.0.0.schema.json\n---\n\n# Saved view\n\nA record exposed through `mdbase.view` stores shared query scope and one or more\nstable named views. Each named view resolves to the query model in Chapter 11.\nOptional `presentation` metadata is advisory and does not alter headless query\nresults.\n\nImplementing this contract is how a collection declares which of its records\nare saved views. A tool that advertises `view_records` discovers and executes\nviews through this contract's type implementations, not through a reserved type\nname, path, or frontmatter value.\n\nThis artifact is passive. Implementing it grants no authority to read, execute,\nor modify any record.\n" + }, + { + "source": "types/view/1.md", + "document": "---\nkind: mdbase.type\nname: view\nversion: 1\ndescription: 'Saved views: shared query scope and stable named views'\nmatch:\n where:\n type: view\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: https://json-schema.org/draft/2020-12/schema\n title: View\n description: 'Saved views: shared query scope and stable named views.'\n type: object\n required:\n - type\n - id\n - version\n - name\n - views\n properties:\n type:\n const: view\n id:\n $ref: '#/$defs/identifier'\n version:\n type: integer\n minimum: 1\n name:\n type: string\n minLength: 1\n description:\n type: string\n query:\n $ref: '#/$defs/sharedQuery'\n properties:\n $ref: '#/$defs/propertyMetadataSet'\n summary_functions:\n $ref: '#/$defs/summaryFunctionSet'\n views:\n type: array\n minItems: 1\n items:\n $ref: '#/$defs/view'\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n $defs:\n identifier:\n type: string\n pattern: ^[A-Za-z][A-Za-z0-9._:-]*$\n typeName:\n type: string\n pattern: ^[A-Za-z][A-Za-z0-9_-]{0,127}$\n fieldName:\n type: string\n pattern: ^[A-Za-z_][A-Za-z0-9_:-]*$\n expression:\n type: string\n minLength: 1\n extension:\n type: object\n additionalProperties: true\n typeList:\n type: array\n minItems: 1\n uniqueItems: true\n items:\n $ref: '#/$defs/typeName'\n sharedQuery:\n type: object\n properties:\n types:\n $ref: '#/$defs/typeList'\n where:\n $ref: '#/$defs/expression'\n context:\n $ref: '#/$defs/viewContext'\n projections:\n $ref: '#/$defs/projectionSet'\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n viewContext:\n type: object\n required:\n - this\n properties:\n this:\n $ref: '#/$defs/thisContext'\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n thisContext:\n type: object\n properties:\n on_missing:\n enum:\n - view\n - 'null'\n - error\n default: view\n types:\n $ref: '#/$defs/typeList'\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n projectionSet:\n type: object\n propertyNames:\n $ref: '#/$defs/fieldName'\n additionalProperties:\n $ref: '#/$defs/projection'\n projection:\n type: object\n required:\n - expr\n properties:\n expr:\n $ref: '#/$defs/expression'\n description:\n type: string\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n propertyMetadataSet:\n type: object\n propertyNames:\n type: string\n minLength: 1\n additionalProperties:\n $ref: '#/$defs/propertyMetadata'\n propertyMetadata:\n type: object\n properties:\n label:\n type: string\n description:\n type: string\n format:\n type: string\n hidden:\n type: boolean\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n summaryFunctionSet:\n type: object\n propertyNames:\n $ref: '#/$defs/fieldName'\n additionalProperties:\n $ref: '#/$defs/summaryFunction'\n summaryFunction:\n type: object\n required:\n - expr\n properties:\n expr:\n $ref: '#/$defs/expression'\n description:\n type: string\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n view:\n type: object\n required:\n - id\n - name\n properties:\n id:\n $ref: '#/$defs/identifier'\n name:\n type: string\n minLength: 1\n description:\n type: string\n types:\n $ref: '#/$defs/typeList'\n where:\n $ref: '#/$defs/expression'\n context:\n $ref: '#/$defs/viewContext'\n projections:\n $ref: '#/$defs/projectionSet'\n select:\n $ref: '#/$defs/select'\n order_by:\n $ref: '#/$defs/orderBy'\n group_by:\n $ref: '#/$defs/groupBy'\n summaries:\n $ref: '#/$defs/summaries'\n limit:\n type: integer\n minimum: 0\n offset:\n type: integer\n minimum: 0\n include_body:\n type: boolean\n default: false\n frontmatter_mode:\n enum:\n - effective\n - persisted\n - both\n default: effective\n presentation:\n $ref: '#/$defs/presentation'\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n select:\n type: array\n minItems: 1\n items:\n oneOf:\n - type: string\n minLength: 1\n - $ref: '#/$defs/selectExpression'\n selectExpression:\n type: object\n required:\n - name\n - expr\n properties:\n name:\n $ref: '#/$defs/fieldName'\n expr:\n $ref: '#/$defs/expression'\n label:\n type: string\n description:\n type: string\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\n orderBy:\n type: array\n minItems: 1\n items:\n type: object\n required:\n - field\n properties:\n field:\n type: string\n minLength: 1\n direction:\n enum:\n - asc\n - desc\n default: asc\n additionalProperties: false\n groupBy:\n type: array\n minItems: 1\n items:\n type: object\n required:\n - field\n properties:\n field:\n type: string\n minLength: 1\n direction:\n enum:\n - asc\n - desc\n default: asc\n additionalProperties: false\n summaries:\n type: array\n minItems: 1\n items:\n $ref: '#/$defs/summary'\n summary:\n type: object\n required:\n - field\n - function\n properties:\n field:\n type: string\n minLength: 1\n function:\n $ref: '#/$defs/identifier'\n name:\n $ref: '#/$defs/fieldName'\n label:\n type: string\n additionalProperties: false\n presentation:\n type: object\n required:\n - type\n properties:\n type:\n $ref: '#/$defs/identifier'\n fallback:\n $ref: '#/$defs/identifier'\n mappings:\n type: object\n propertyNames:\n $ref: '#/$defs/fieldName'\n additionalProperties:\n type: string\n minLength: 1\n options:\n type: object\n additionalProperties: true\n patternProperties:\n ^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$:\n $ref: '#/$defs/extension'\n additionalProperties: false\nimplements:\n- contract: mdbase.view\n version: 1.0.0\n fields:\n id: id\n version: version\n name: name\n description: description\n query: query\n properties: properties\n summary_functions: summary_functions\n views: views\ncollection:\n display:\n name_field: name\n---\n\n# View\n\nA view record stores shared query scope and one or more stable named views.\nTools that support saved views discover these records through the `mdbase.view`\ncontract and run each named view as an ordinary query.\n\nThis type belongs to your collection once installed. You can rename fields or\nchange how records are matched, as long as the `implements` mapping still\npoints each contract field at the right frontmatter field.\n" + } + ], + "provides": [ + { + "id": "mdbase.view", + "version": "1.0.0", + "digest": "sha256:0918acefce08cddd778fab768fe4f2e4615d263a0eec3e5330b6897c686d744e" + } + ] +} diff --git a/public/contracts/packs/obsidian.base/1.0.0.json b/public/contracts/packs/obsidian.base/1.0.0.json new file mode 100644 index 0000000..d2648ab --- /dev/null +++ b/public/contracts/packs/obsidian.base/1.0.0.json @@ -0,0 +1,42 @@ +{ + "manifest": { + "kind": "mdbase.type-pack", + "id": "obsidian.base", + "version": "1.0.0", + "name": "Obsidian Bases", + "description": "The obsidian.base contract and an editable type that stores Obsidian Bases as records.", + "resources": [ + { + "kind": "contract", + "mode": "managed", + "source": "contracts/obsidian.base/1.0.0.md", + "target": "_contracts/obsidian.base/1.0.0.md", + "digest": "sha256:fca77a9c845223784d4af7f5e13e2f149295621ad1b794ea9bb4d99f604467e8" + }, + { + "kind": "type", + "mode": "seed", + "source": "types/obsidian-base/1.md", + "target": "_types/obsidian_base.md", + "digest": "sha256:bfeb3880200bc1bef7fd50b777d3f927ce931d08b4fb3c30d4cddb55feaf3f92" + } + ] + }, + "resources": [ + { + "source": "contracts/obsidian.base/1.0.0.md", + "document": "---\nkind: mdbase.contract\ncontract_type: record\nid: obsidian.base\nversion: 1.0.0\nname: Obsidian Base\ndescription: An Obsidian Bases saved-view source stored as a YAML document record.\nrecord_schema:\n dialect: json-schema-2020-12\n value:\n $schema: https://json-schema.org/draft/2020-12/schema\n type: object\n required: [views]\n properties:\n filters: {}\n formulas: { type: object }\n properties: { type: object }\n views:\n type: array\n minItems: 1\n items: { type: object, required: [type], properties: { type: { type: string }, name: { type: string } } }\n---\n\n# Obsidian Base\n\nA record exposed through `obsidian.base` is an Obsidian Bases source: global\nfilters, formulas, property metadata, and one or more views. The schema is\ndeliberately permissive. Obsidian owns the format and adds keys over time;\nunknown keys are preserved and ignored.\n\nTools that advertise `obsidian_bases_views` discover and execute these records\nthrough this contract and evaluate them with the Obsidian Bases expression\ndialect described in the [Obsidian Bases adapter](https://mdbase.dev/spec/adapters/obsidian-bases).\n\nThis artifact is passive. Implementing it grants no authority to read, execute,\nor modify any record.\n" + }, + { + "source": "types/obsidian-base/1.md", + "document": "---\nkind: mdbase.type\nname: obsidian_base\nversion: 1\ndescription: Obsidian Bases saved views stored as records\nmatch:\n path_glob: '**/*.base'\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: https://json-schema.org/draft/2020-12/schema\n title: Obsidian Base\n description: An Obsidian Bases file. Obsidian owns the format; unknown keys are kept.\n type: object\n required:\n - views\n properties:\n filters: {}\n formulas:\n type: object\n properties:\n type: object\n views:\n type: array\n minItems: 1\n items:\n type: object\n required:\n - type\n properties:\n type:\n type: string\n name:\n type: string\nimplements:\n - contract: obsidian.base\n version: 1.0.0\n fields:\n filters: filters\n formulas: formulas\n properties: properties\n views: views\n---\n\n# Obsidian Base\n\nMakes `.base` files discoverable as saved views through the `obsidian.base`\ncontract. It takes effect when the collection lists `base` in\n`settings.record_extensions`; narrow the `path_glob` to the folders that hold\nyour Bases. Edit Bases with whole-document replacement so Obsidian's comments\nand layout survive.\n" + } + ], + "provides": [ + { + "id": "obsidian.base", + "version": "1.0.0", + "digest": "sha256:37e8a0bfb32bb4795c1b3c5dec613c32429aaa765dc33e4adc0c627a48f5c1ff" + } + ] +} diff --git a/public/contracts/schemas/mdbase.comment/1.0.0.schema.json b/public/contracts/schemas/mdbase.comment/1.0.0.schema.json new file mode 100644 index 0000000..ae810d6 --- /dev/null +++ b/public/contracts/schemas/mdbase.comment/1.0.0.schema.json @@ -0,0 +1,229 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mdbase.dev/contracts/schemas/mdbase.comment/1.0.0.schema.json", + "title": "Comment", + "description": "A comment, reply or suggested edit on a Markdown record, stored as its own record. The comment's Markdown body is its text.", + "type": "object", + "required": [ + "document", + "created_at" + ], + "properties": { + "document": { + "type": "string", + "pattern": "\\S", + "description": "Link to the commented record, such as [[chapters/method]]. Replies repeat their thread's document so one query finds a record's whole discussion." + }, + "in_reply_to": { + "type": "string", + "pattern": "\\S", + "description": "Link to the first comment of the thread this comment replies to. Absent on the first comment of a thread." + }, + "motivation": { + "enum": [ + "commenting", + "replying", + "editing" + ], + "default": "commenting", + "description": "commenting starts a thread, replying answers one, and editing suggests replacing the target's text." + }, + "target": { + "type": "object", + "description": "Where in the document the thread is anchored. Absent means the whole record. Only the first comment of a thread has a target.", + "properties": { + "quote": { + "type": "object", + "description": "The anchored text and the text around it. The quote is authoritative; positions are a hint.", + "required": [ + "exact" + ], + "properties": { + "exact": { + "type": "string", + "description": "The anchored text exactly as it appears in the record body. Empty for an insertion point, which prefix or suffix then locates." + }, + "prefix": { + "type": "string", + "description": "Text immediately before exact, to tell repeated occurrences apart." + }, + "suffix": { + "type": "string", + "description": "Text immediately after exact, to tell repeated occurrences apart." + } + }, + "if": { + "required": [ + "exact" + ], + "properties": { + "exact": { + "type": "string", + "maxLength": 0 + } + } + }, + "then": { + "anyOf": [ + { + "required": [ + "prefix" + ], + "properties": { + "prefix": { + "type": "string", + "minLength": 1 + } + } + }, + { + "required": [ + "suffix" + ], + "properties": { + "suffix": { + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "additionalProperties": false + }, + "text_position": { + "type": "object", + "description": "Offsets of the quote in one revision of the record body.", + "required": [ + "basis", + "unit", + "start", + "end" + ], + "properties": { + "basis": { + "type": "object", + "required": [ + "profile", + "hash" + ], + "properties": { + "profile": { + "const": "markdown-body", + "description": "Offsets count the record's Markdown body as stored, after the frontmatter, so frontmatter edits never move them." + }, + "hash": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$", + "description": "SHA-256 of the UTF-8 body the offsets were measured in." + } + }, + "additionalProperties": false + }, + "unit": { + "const": "unicode_code_point" + }, + "start": { + "type": "integer", + "minimum": 0 + }, + "end": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": [ + "quote" + ], + "additionalProperties": false + }, + "suggestion": { + "type": "object", + "description": "The suggested edit of an editing comment: replace the target's quote with replacement.", + "required": [ + "replacement" + ], + "properties": { + "replacement": { + "type": "string", + "description": "The text to put in place of the quote. Empty suggests deleting it." + }, + "outcome": { + "enum": [ + "accepted", + "rejected" + ], + "description": "What happened to the suggestion once its thread was resolved." + } + }, + "additionalProperties": false + }, + "status": { + "enum": [ + "open", + "resolved" + ], + "default": "open", + "description": "Whether the thread is still open. Only meaningful on the first comment of a thread." + }, + "resolved_by": { + "type": "string", + "pattern": "\\S", + "description": "Link to the mdbase.person record of who resolved the thread." + }, + "resolved_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string", + "pattern": "\\S", + "description": "Link to the mdbase.person record of the comment's author. Absent when the author has no person record." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "modified_at": { + "type": "string", + "format": "date-time" + }, + "deleted_at": { + "type": "string", + "format": "date-time", + "description": "When the comment was withdrawn. The record stays so its thread and replies keep their shape; its body should be emptied." + } + }, + "allOf": [ + { + "if": { + "properties": { + "motivation": { + "const": "editing" + } + }, + "required": [ + "motivation" + ] + }, + "then": { + "required": [ + "target", + "suggestion" + ], + "properties": { + "target": { + "type": "object" + }, + "suggestion": { + "type": "object" + } + } + } + } + ], + "additionalProperties": false +} diff --git a/public/contracts/schemas/mdbase.view/1.0.0.schema.json b/public/contracts/schemas/mdbase.view/1.0.0.schema.json new file mode 100644 index 0000000..d98e82f --- /dev/null +++ b/public/contracts/schemas/mdbase.view/1.0.0.schema.json @@ -0,0 +1,261 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mdbase.dev/schemas/v0.3/view.schema.json", + "title": "mdbase.view 1.0.0 record contract view", + "description": "The contract view of a saved-view record. A type implements mdbase.view by mapping these fields; the optional type property only lets the canonical view type validate its own membership value.", + "type": "object", + "required": ["id", "version", "name", "views"], + "properties": { + "type": { "type": "string", "minLength": 1 }, + "id": { "$ref": "#/$defs/identifier" }, + "version": { "type": "integer", "minimum": 1 }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "query": { "$ref": "#/$defs/sharedQuery" }, + "properties": { "$ref": "#/$defs/propertyMetadataSet" }, + "summary_functions": { "$ref": "#/$defs/summaryFunctionSet" }, + "views": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/view" } + } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false, + "$defs": { + "identifier": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9._:-]*$" + }, + "typeName": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,127}$" + }, + "fieldName": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_:-]*$" + }, + "expression": { + "type": "string", + "minLength": 1 + }, + "extension": { + "type": "object", + "additionalProperties": true + }, + "typeList": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/typeName" } + }, + "sharedQuery": { + "type": "object", + "properties": { + "types": { "$ref": "#/$defs/typeList" }, + "where": { "$ref": "#/$defs/expression" }, + "context": { "$ref": "#/$defs/viewContext" }, + "projections": { "$ref": "#/$defs/projectionSet" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "viewContext": { + "type": "object", + "required": ["this"], + "properties": { + "this": { "$ref": "#/$defs/thisContext" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "thisContext": { + "type": "object", + "properties": { + "on_missing": { + "enum": ["view", "null", "error"], + "default": "view" + }, + "types": { "$ref": "#/$defs/typeList" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "projectionSet": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/fieldName" }, + "additionalProperties": { "$ref": "#/$defs/projection" } + }, + "projection": { + "type": "object", + "required": ["expr"], + "properties": { + "expr": { "$ref": "#/$defs/expression" }, + "description": { "type": "string" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "propertyMetadataSet": { + "type": "object", + "propertyNames": { "type": "string", "minLength": 1 }, + "additionalProperties": { "$ref": "#/$defs/propertyMetadata" } + }, + "propertyMetadata": { + "type": "object", + "properties": { + "label": { "type": "string" }, + "description": { "type": "string" }, + "format": { "type": "string" }, + "hidden": { "type": "boolean" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "summaryFunctionSet": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/fieldName" }, + "additionalProperties": { "$ref": "#/$defs/summaryFunction" } + }, + "summaryFunction": { + "type": "object", + "required": ["expr"], + "properties": { + "expr": { "$ref": "#/$defs/expression" }, + "description": { "type": "string" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "view": { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "$ref": "#/$defs/identifier" }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "types": { "$ref": "#/$defs/typeList" }, + "where": { "$ref": "#/$defs/expression" }, + "context": { "$ref": "#/$defs/viewContext" }, + "projections": { "$ref": "#/$defs/projectionSet" }, + "select": { "$ref": "#/$defs/select" }, + "order_by": { "$ref": "#/$defs/orderBy" }, + "group_by": { "$ref": "#/$defs/groupBy" }, + "summaries": { "$ref": "#/$defs/summaries" }, + "limit": { "type": "integer", "minimum": 0 }, + "offset": { "type": "integer", "minimum": 0 }, + "include_body": { "type": "boolean", "default": false }, + "frontmatter_mode": { + "enum": ["effective", "persisted", "both"], + "default": "effective" + }, + "presentation": { "$ref": "#/$defs/presentation" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "select": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { "type": "string", "minLength": 1 }, + { "$ref": "#/$defs/selectExpression" } + ] + } + }, + "selectExpression": { + "type": "object", + "required": ["name", "expr"], + "properties": { + "name": { "$ref": "#/$defs/fieldName" }, + "expr": { "$ref": "#/$defs/expression" }, + "label": { "type": "string" }, + "description": { "type": "string" } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + }, + "orderBy": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["field"], + "properties": { + "field": { "type": "string", "minLength": 1 }, + "direction": { "enum": ["asc", "desc"], "default": "asc" } + }, + "additionalProperties": false + } + }, + "groupBy": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["field"], + "properties": { + "field": { "type": "string", "minLength": 1 }, + "direction": { "enum": ["asc", "desc"], "default": "asc" } + }, + "additionalProperties": false + } + }, + "summaries": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/summary" } + }, + "summary": { + "type": "object", + "required": ["field", "function"], + "properties": { + "field": { "type": "string", "minLength": 1 }, + "function": { "$ref": "#/$defs/identifier" }, + "name": { "$ref": "#/$defs/fieldName" }, + "label": { "type": "string" } + }, + "additionalProperties": false + }, + "presentation": { + "type": "object", + "required": ["type"], + "properties": { + "type": { "$ref": "#/$defs/identifier" }, + "fallback": { "$ref": "#/$defs/identifier" }, + "mappings": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/fieldName" }, + "additionalProperties": { "type": "string", "minLength": 1 } + }, + "options": { + "type": "object", + "additionalProperties": true + } + }, + "patternProperties": { + "^x-[A-Za-z][A-Za-z0-9._:-]{0,127}$": { "$ref": "#/$defs/extension" } + }, + "additionalProperties": false + } + } +} diff --git a/site-sources.json b/site-sources.json index b33d741..a1b43e6 100644 --- a/site-sources.json +++ b/site-sources.json @@ -9,7 +9,7 @@ }, "contracts": { "repository": "mdbase-dev/mdbase-contracts", - "ref": "792c5e74521fe7b99a942b9e9e2510c4ca1857cc" + "ref": "6fee9ea66cf06a396aa064e67a4cd5159ecc2a92" }, "implementations": [ { diff --git a/src/data/contracts.json b/src/data/contracts.json index 443a622..174ca55 100644 --- a/src/data/contracts.json +++ b/src/data/contracts.json @@ -9,6 +9,16 @@ "url": "https://mdbase.dev/" }, "contracts": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "name": "Comment", + "description": "Comments, replies and suggested edits anchored to text in Markdown records.", + "contract_type": "record", + "digest": "sha256:c85663fbb339d33e8e67825c81fc160fbac00285a1687cbe99187bad7f58878d", + "artifact": "./artifacts/contracts/mdbase.comment/1.0.0.md", + "standards": [] + }, { "id": "mdbase.contact", "version": "1.0.0", @@ -199,6 +209,26 @@ "artifact": "./artifacts/contracts/mdbase.runtime.workflow/1.0.0.md", "standards": [] }, + { + "id": "mdbase.view", + "version": "1.0.0", + "name": "Saved view", + "description": "Shared query scope and stable named views executable through the Query profile.", + "contract_type": "record", + "digest": "sha256:91eae42371c2d411b20d3c976aece3071bbd753963b77b6bcdc26137a8fdcded", + "artifact": "./artifacts/contracts/mdbase.view/1.0.0.md", + "standards": [] + }, + { + "id": "obsidian.base", + "version": "1.0.0", + "name": "Obsidian Base", + "description": "An Obsidian Bases saved-view source stored as a YAML document record.", + "contract_type": "record", + "digest": "sha256:fca77a9c845223784d4af7f5e13e2f149295621ad1b794ea9bb4d99f604467e8", + "artifact": "./artifacts/contracts/obsidian.base/1.0.0.md", + "standards": [] + }, { "id": "tasknotes.task", "version": "0.3.0-rc.3", @@ -221,6 +251,44 @@ } ], "packs": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "name": "Comments type pack", + "description": "Comments, replies and suggested edits anchored to text in Markdown records.", + "digest": "sha256:37729ce8eca79ed52b669b191350056cbd23d2fe1453d38b56805979ce0d9e29", + "provision": "./packs/mdbase.comment/1.0.0.json", + "provides": [ + { + "id": "mdbase.comment", + "version": "1.0.0", + "digest": "sha256:c2c5b3f3013d10625310401e9f9149ffa2d09c5b7d91b4fc97688d3ac010bec5" + } + ], + "resource_count": 3, + "display": { + "name": "Comments", + "summary": "Discuss and suggest edits to notes without changing their text, in any app that reads comments.", + "category": "work", + "audience": "general", + "icon": "chat-circle", + "badges": [ + "Portable comments", + "Suggested edits" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "optional", + "primary_type": "comment", + "types": [ + { + "name": "comment", + "label": "Comment" + } + ] + } + }, { "id": "mdbase.contact", "version": "1.0.0", @@ -465,6 +533,80 @@ ] } }, + { + "id": "mdbase.view", + "version": "1.0.0", + "name": "Saved views", + "description": "The mdbase.view contract and an editable View type for saved, reusable queries.", + "digest": "sha256:d2a3ff8629699b183bb8117d7499697fecc7685ed8558cae6d9a87311d7cfd05", + "provision": "./packs/mdbase.view/1.0.0.json", + "provides": [ + { + "id": "mdbase.view", + "version": "1.0.0", + "digest": "sha256:0918acefce08cddd778fab768fe4f2e4615d263a0eec3e5330b6897c686d744e" + } + ], + "resource_count": 3, + "display": { + "name": "Saved views", + "summary": "Save reusable queries that any view-aware app can list and run.", + "category": "work", + "audience": "general", + "icon": "table", + "badges": [ + "Portable views" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "optional", + "primary_type": "view", + "types": [ + { + "name": "view", + "label": "View" + } + ] + } + }, + { + "id": "obsidian.base", + "version": "1.0.0", + "name": "Obsidian Bases", + "description": "The obsidian.base contract and an editable type that stores Obsidian Bases as records.", + "digest": "sha256:8251b351a4b00c4ea3f4d49988ae45ab52ceaf2420f57ce8105ca4cfe5eafd50", + "provision": "./packs/obsidian.base/1.0.0.json", + "provides": [ + { + "id": "obsidian.base", + "version": "1.0.0", + "digest": "sha256:37e8a0bfb32bb4795c1b3c5dec613c32429aaa765dc33e4adc0c627a48f5c1ff" + } + ], + "resource_count": 2, + "display": { + "name": "Obsidian Bases", + "summary": "Use Obsidian Bases files as saved views that any view-aware app can list and run.", + "category": "work", + "audience": "general", + "icon": "table", + "badges": [ + "Obsidian compatible" + ] + }, + "installation": { + "visibility": "default", + "recommendation": "optional", + "primary_type": "obsidian_base", + "types": [ + { + "name": "obsidian_base", + "label": "Obsidian base" + } + ] + } + }, { "id": "tasknotes.task", "version": "0.3.0-rc.12",