mindoodb-app-sdk 0.0.53 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,26 +22,17 @@ A **MindooDB App** is any web application that uses this SDK to communicate with
22
22
 
23
23
  ## Architecture
24
24
 
25
- ```
26
- ┌─────────────────────────────────────────────────────────┐
27
- │ Haven (browser tab) │
28
- │ │
29
- │ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐ │
30
- │ │ Haven UI │──▶│ Bridge Host │──▶│ MindooDB │ │
31
- │ │ (theme, │ │ (RPC + │ │ (encrypted │ │
32
- │ │ viewport) │ │ streams) │ │ databases) │ │
33
- │ └──────────────┘ └──────┬──────┘ └─────────────┘ │
34
- │ │ │
35
- │ postMessage + MessagePort │
36
- │ │ │
37
- │ ┌─────────────────────────┼─────────────────────────┐ │
38
- │ │ Your App (sandboxed iframe / window) │ │
39
- │ │ │ │ │
40
- │ │ ┌──────────▼──────────┐ │ │
41
- │ │ │ mindoodb-app-sdk │ │ │
42
- │ │ └─────────────────────┘ │ │
43
- │ └───────────────────────────────────────────────────┘ │
44
- └─────────────────────────────────────────────────────────┘
25
+ ```mermaid
26
+ flowchart TB
27
+ subgraph haven["Haven (browser tab)"]
28
+ direction TB
29
+ ui["Haven UI<br/>(theme, viewport)"] --> bridge["Bridge Host<br/>(RPC + streams)"]
30
+ bridge --> db["MindooDB<br/>(encrypted databases)"]
31
+ subgraph app["Your App (sandboxed iframe / window)"]
32
+ sdk["mindoodb-app-sdk"]
33
+ end
34
+ bridge -- "postMessage + MessagePort" --> sdk
35
+ end
45
36
  ```
46
37
 
47
38
  The bridge carries RPC calls (documents, views, database info), binary attachment streams, and push events (theme changes, viewport resizes) over a single `MessagePort`.
@@ -757,23 +748,11 @@ Two layers are available:
757
748
 
758
749
  The end-to-end flow is the same in both cases:
759
750
 
760
- ```
761
- ┌────────────────────────────────────────────────────┐
762
- edit ───► │ App buffer (local string + pending splices) │ ──► flush
763
- └─────────────────────────┬──────────────────────────┘
764
- │ documents.update({
765
- │ text: [{ path, baseHeads, edits }]
766
- │ })
767
- ▼
768
- ┌────────────────────────────────────────────────────┐
769
- │ Haven applies edits at `baseHeads` and merges with │
770
- │ concurrent changes via Automerge │
771
- └─────────────────────────┬──────────────────────────┘
772
- │ canonical merged document
773
- ▼
774
- ┌────────────────────────────────────────────────────┐
775
- │ App reconciles its editor with the canonical text │
776
- └────────────────────────────────────────────────────┘
751
+ ```mermaid
752
+ flowchart TB
753
+ edit(["edit"]) --> buffer["App buffer<br/>(local string + pending splices)"]
754
+ buffer -- "flush: documents.update({ text: [{ path, baseHeads, edits }] })" --> merge["Haven applies edits at baseHeads<br/>and merges with concurrent changes via Automerge"]
755
+ merge -- "canonical merged document" --> reconcile["App reconciles its editor<br/>with the canonical text"]
777
756
  ```
778
757
 
779
758
  #### Text patch shape
@@ -1003,24 +982,14 @@ If you must use binary sync, Haven decrypts the canonical document, merges incom
1003
982
 
1004
983
  Typical flow:
1005
984
 
1006
- ```
1007
- ┌────────────────────────────────────────────────────┐
1008
- open ──► │ getAutomergeSnapshot → load local replica │
1009
- └─────────────────────────┬──────────────────────────┘
1010
- │
1011
- edit ──► │ Local Automerge doc (or span edits applied locally) │
1012
- └─────────────────────────┬──────────────────────────┘
1013
- │ getChangesSince(baseHeads)
1014
- ▼
1015
- ┌────────────────────────────────────────────────────┐
1016
- save ──► │ applyAutomergeChanges({ baseHeads, replicaHeads, │
1017
- │ changes }) │
1018
- └─────────────────────────┬──────────────────────────┘
1019
- │ Haven merges + persists
1020
- ▼
1021
- ┌────────────────────────────────────────────────────┐
1022
- │ Apply result.changesSince locally (or skip if empty) │
1023
- └────────────────────────────────────────────────────┘
985
+ ```mermaid
986
+ flowchart TB
987
+ open(["open"]) --> snapshot["getAutomergeSnapshot<br/>→ load local replica"]
988
+ snapshot --> local["Local Automerge doc<br/>(or span edits applied locally)"]
989
+ editStep(["edit"]) --> local
990
+ local -- "getChangesSince(baseHeads)" --> apply["applyAutomergeChanges({<br/>baseHeads, replicaHeads, changes })"]
991
+ saveStep(["save"]) --> apply
992
+ apply -- "Haven merges + persists" --> result["Apply result.changesSince locally<br/>(or skip if empty)"]
1024
993
  ```
1025
994
 
1026
995
  Bridge RPCs: `documents.automerge.getSnapshot` and `documents.automerge.applyChanges`.
@@ -1702,6 +1671,7 @@ What the page shows comes from the optional `listing` in `haven-app.json`. Haven
1702
1671
  "listing": {
1703
1672
  "summary": { "en": "Plan trips together.", "de": "Reisen gemeinsam planen." },
1704
1673
  "description": { "en": "First paragraph.\n\nSecond paragraph." },
1674
+ "descriptionMarkdown": { "en": "**Plan** together.\n\n- Shared board\n- [Website](https://trips.example.com)" },
1705
1675
  "icon": "icon.svg",
1706
1676
  "screenshots": [{ "file": "screenshots/board.webp", "caption": { "en": "The trip board" } }],
1707
1677
  "publisher": { "name": "Mindoo GmbH", "url": "https://mindoo.de" }
@@ -1711,6 +1681,10 @@ What the page shows comes from the optional `listing` in `haven-app.json`. Haven
1711
1681
 
1712
1682
  Texts are a string or a per-locale map with an `en` fallback. Image paths are relative to the app origin or absolute `https:` URLs. The listing is display-only and grants nothing.
1713
1683
 
1684
+ Opening the app URL directly also puts share tags into `index.html` (`twitter:card` = `summary`, plus the Open Graph tags other apps read), with the app icon as the image. A production build takes the absolute URL from `publicUrl` in `haven-app.json`. `vite dev` uses the dev server's own origin instead, so `http://127.0.0.1:<port>/` previews against itself. Crawlers do not run the landing page's JavaScript, which is why the tags are in the HTML file.
1685
+
1686
+ `description` is plain text. `descriptionMarkdown` is the same text with formatting, in a small Markdown subset: paragraphs, headings, bullet and numbered lists, `**bold**`, `_italic_`, `` `code` ``, `[text](https://…)`, `[text](mailto:…)` and bare `https://…` links. Links to any other scheme render as plain text. Readers that know `descriptionMarkdown` show it instead of `description`; older Haven versions ignore it, so set both if they matter. `listingMarkdownToPlainText()` derives the plain version. A publisher `url` (`https:` only) turns the publisher name into a link.
1687
+
1714
1688
  For automated and manual tests use a separate test URL that frames the app with a mock host instead — see "Browser test host" in [`TESTING.md`](./TESTING.md). End users never see that page, and they never see mock data.
1715
1689
 
1716
1690
  ### Haven-hosted bundles
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppDragBindSourceOptions, b as MindooDBAppDragStartInput, c as MindooDBAppDragStartResult, d as MindooDBAppDragPreview, e as MindooDBAppBridge, f as MindooDBAppHostShortcutBinding, g as MindooDBAppHostShortcutAction, h as MindooDBAppHostingMode, i as MindooDBAppDefinition, j as MindooDBAppDatabase, k as MindooDBAppDocument, l as MindooDBAppRichTextSpan, m as MindooDBAppRichTextSnapshot, n as MindooDBAppCreateDocumentInput } from './types-CsKw_niD.js';
2
- export { o as MINDOODB_APP_DEFINITION_FILE_NAME, p as MINDOODB_APP_DEFINITION_FORMAT, q as MINDOODB_APP_DEFINITION_PERMISSIONS, r as MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS, s as MINDOODB_APP_DEFINITION_VERSION, t as MINDOODB_APP_LISTING_DESCRIPTION_MAX, u as MINDOODB_APP_LISTING_MAX_SCREENSHOTS, v as MINDOODB_APP_LISTING_SUMMARY_MAX, w as MINDOODB_APP_SEAL_SIGNING_DOMAIN, x as MINDOODB_APP_WELL_KNOWN_DRAG_TYPES, y as MindooDBAppAccessDecision, z as MindooDBAppAccessTier, A as MindooDBAppAttachmentApi, B as MindooDBAppAttachmentChunk, C as MindooDBAppAttachmentInfo, D as MindooDBAppAttachmentPreviewOptions, E as MindooDBAppAttachmentPreviewSession, F as MindooDBAppAutomergeChangesPatch, G as MindooDBAppAutomergeChangesSince, H as MindooDBAppAutomergeGetOptions, I as MindooDBAppAutomergePatchResult, J as MindooDBAppAutomergeSnapshot, K as MindooDBAppBridgeBeforeCloseAck, L as MindooDBAppBridgeBeforeCloseMessage, N as MindooDBAppBridgeConnectMessage, O as MindooDBAppBridgeConnectOptions, P as MindooDBAppBridgeConnectedMessage, Q as MindooDBAppBridgeDragDropMessage, R as MindooDBAppBridgeDragLeaveMessage, S as MindooDBAppBridgeDragOverMessage, T as MindooDBAppBridgeErrorPayload, U as MindooDBAppBridgeHandshakeErrorMessage, V as MindooDBAppBridgeHostFocusChangedMessage, W as MindooDBAppBridgeLocaleChangedMessage, X as MindooDBAppBridgePortMessage, Y as MindooDBAppBridgeQueryResultMessage, Z as MindooDBAppBridgeRpcError, _ as MindooDBAppBridgeRpcMessage, $ as MindooDBAppBridgeRpcRequest, a0 as MindooDBAppBridgeRpcSuccess, a1 as MindooDBAppBridgeShortcutInvokedMessage, a2 as MindooDBAppBridgeShortcutsChangedMessage, a3 as MindooDBAppBridgeStreamAbortRequest, a4 as MindooDBAppBridgeStreamAck, a5 as MindooDBAppBridgeStreamCloseRequest, a6 as MindooDBAppBridgeStreamError, a7 as MindooDBAppBridgeStreamMessage, a8 as MindooDBAppBridgeStreamOpenResult, a9 as MindooDBAppBridgeStreamReadRequest, aa as MindooDBAppBridgeStreamWriteRequest, ab as MindooDBAppBridgeThemeChangedMessage, ac as MindooDBAppBridgeUiPreferencesChangedMessage, ad as MindooDBAppBridgeViewChangedMessage, ae as MindooDBAppBridgeViewportChangedMessage, af as MindooDBAppBridgeWorkspaceFocusRequestedMessage, ag as MindooDBAppCapability, ah as MindooDBAppConfiguredViewCategorizationStyle, ai as MindooDBAppConfiguredViewColumn, aj as MindooDBAppConfiguredViewColumnRole, ak as MindooDBAppConfiguredViewExpressionMode, al as MindooDBAppConfiguredViewFieldExpression, am as MindooDBAppConfiguredViewFilterDefinition, an as MindooDBAppConfiguredViewFilterMode, ao as MindooDBAppConfiguredViewFilterRule, ap as MindooDBAppConfiguredViewFormulaExpression, aq as MindooDBAppConfiguredViewFormulaFilterDefinition, ar as MindooDBAppConfiguredViewPreviewMode, as as MindooDBAppConfiguredViewRuleFilterDefinition, at as MindooDBAppConfiguredViewRuleMatchMode, au as MindooDBAppConfiguredViewRuleOperator, av as MindooDBAppConfiguredViewValueExpression, aw as MindooDBAppCreateKeyInfo, ax as MindooDBAppCreateViewInput, ay as MindooDBAppCreateViewNavigatorInput, az as MindooDBAppDatabaseInfo, aA as MindooDBAppDefinitionDatabase, aB as MindooDBAppDefinitionListing, aC as MindooDBAppDefinitionPermission, aD as MindooDBAppDefinitionRegistrationPermission, aE as MindooDBAppDefinitionScreenshot, aF as MindooDBAppDefinitionValidation, aG as MindooDBAppDirectoryApi, aH as MindooDBAppDirectoryEntry, aI as MindooDBAppDocumentApi, aJ as MindooDBAppDocumentHeadCursorResult, aK as MindooDBAppDocumentHistoryEntry, aL as MindooDBAppDocumentListQuery, aM as MindooDBAppDocumentListResult, aN as MindooDBAppDocumentQuery, aO as MindooDBAppDocumentRevisionId, aP as MindooDBAppDocumentScanPreset, aQ as MindooDBAppDocumentSummary, aR as MindooDBAppDragApi, aS as MindooDBAppDragCancelReason, aT as MindooDBAppDragDropEvent, aU as MindooDBAppDragEffect, aV as MindooDBAppDragHoverResult, aW as MindooDBAppDragOffer, aX as MindooDBAppDragOverEvent, aY as MindooDBAppDragPointer, aZ as MindooDBAppDragProfile, a_ as MindooDBAppEntryAttachmentRef, a$ as MindooDBAppEntryProvenance, b0 as MindooDBAppExtractDocumentTextInput, b1 as MindooDBAppExtractDocumentTextResult, b2 as MindooDBAppExtractTextFormat, b3 as MindooDBAppExtractionSetup, b4 as MindooDBAppFulltextSetup, b5 as MindooDBAppHistoricalDocument, b6 as MindooDBAppHistoryQuery, b7 as MindooDBAppHostTheme, b8 as MindooDBAppIdentityApi, b9 as MindooDBAppInaccessibleDocument, ba as MindooDBAppInaccessibleDocumentListQuery, bb as MindooDBAppInaccessibleDocumentListResult, bc as MindooDBAppJsonCounterIncrementPatch, bd as MindooDBAppJsonListDeletePatch, be as MindooDBAppJsonListInsertPatch, bf as MindooDBAppJsonPatch, bg as MindooDBAppJsonSetPatch, bh as MindooDBAppJsonTextMarkPatch, bi as MindooDBAppJsonTextSplicePatch, bj as MindooDBAppJsonTextUnmarkPatch, bk as MindooDBAppJsonUnsetPatch, bl as MindooDBAppLaunchContext, bm as MindooDBAppLiveQuerySubscription, bn as MindooDBAppLocalizedText, bo as MindooDBAppMenuAnchor, bp as MindooDBAppMenuApi, bq as MindooDBAppMenuCommandItem, br as MindooDBAppMenuDismissReason, bs as MindooDBAppMenuDismissedResult, bt as MindooDBAppMenuItem, bu as MindooDBAppMenuKind, bv as MindooDBAppMenuPlacement, bw as MindooDBAppMenuPointAnchor, bx as MindooDBAppMenuRect, by as MindooDBAppMenuRectAnchor, bz as MindooDBAppMenuSelectionResult, bA as MindooDBAppMenuSeparatorItem, bB as MindooDBAppNotificationSeverity, bC as MindooDBAppNotifyInput, bD as MindooDBAppNotifyResult, bE as MindooDBAppProposeAppInput, bF as MindooDBAppProposeAppResult, bG as MindooDBAppQueryCoverage, bH as MindooDBAppQueryInclude, bI as MindooDBAppQueryIncludeCardinality, bJ as MindooDBAppQueryIncludeSlots, bK as MindooDBAppQueryResult, bL as MindooDBAppQueryRow, bM as MindooDBAppQuerySortKey, bN as MindooDBAppQueryTextClause, bO as MindooDBAppReadableAttachmentStream, bP as MindooDBAppRecipientOptions, bQ as MindooDBAppResolvedViewDefinition, bR as MindooDBAppResolvedViewSource, bS as MindooDBAppRevisionPhase, bT as MindooDBAppRevisionVerification, bU as MindooDBAppRichTextBlockSpan, bV as MindooDBAppRichTextGetOptions, bW as MindooDBAppRichTextImmutableString, bX as MindooDBAppRichTextMarkRange, bY as MindooDBAppRichTextMaterializeValue, bZ as MindooDBAppRichTextPatch, b_ as MindooDBAppRichTextScalar, b$ as MindooDBAppRichTextSpliceStep, c0 as MindooDBAppRichTextStep, c1 as MindooDBAppRichTextStepPatch, c2 as MindooDBAppRichTextTextSpan, c3 as MindooDBAppRuntime, c4 as MindooDBAppScanAttachmentOptions, c5 as MindooDBAppScanAttachmentResult, c6 as MindooDBAppScopedDocId, c7 as MindooDBAppScopedDocumentSummary, c8 as MindooDBAppSealIntent, c9 as MindooDBAppSealedChannelOpenInput, ca as MindooDBAppSealedChannelOpenResult, cb as MindooDBAppSealedChannelRequestInput, cc as MindooDBAppSession, cd as MindooDBAppShowMenuInput, ce as MindooDBAppShowMenuResult, cf as MindooDBAppSignPreview, cg as MindooDBAppSignPreviewScope, ch as MindooDBAppSignStatementInput, ci as MindooDBAppSignStatementResult, cj as MindooDBAppStorageApi, ck as MindooDBAppSummarySetup, cl as MindooDBAppTextCursorOptions, cm as MindooDBAppTextCursorPosition, cn as MindooDBAppTextCursorPositionsResult, co as MindooDBAppTextCursorsResult, cp as MindooDBAppTextEdit, cq as MindooDBAppTextPatch, cr as MindooDBAppThemeMode, cs as MindooDBAppTimestampApi, ct as MindooDBAppTimestampInput, cu as MindooDBAppTimestampProvider, cv as MindooDBAppTimestampResult, cw as MindooDBAppUiPreferences, cx as MindooDBAppUpdateDocumentInput, cy as MindooDBAppViewCursorDocumentListResult, cz as MindooDBAppViewCursorResult, cA as MindooDBAppViewEntry, cB as MindooDBAppViewEntryKind, cC as MindooDBAppViewNavigator, cD as MindooDBAppViewNavigatorExpansionState, cE as MindooDBAppViewNavigatorOpenOptions, cF as MindooDBAppViewNavigatorPageOptions, cG as MindooDBAppViewNavigatorPageResult, cH as MindooDBAppViewNavigatorRangeQuery, cI as MindooDBAppViewNavigatorSelectionState, cJ as MindooDBAppViewUpdateStats, cK as MindooDBAppViewport, cL as MindooDBAppWellKnownDragType, cM as MindooDBAppWritableAttachmentStream, cN as resolveMindooDBAppDefinitionUrl, cO as resolveMindooDBAppListingAssetUrl, cP as resolveMindooDBAppLocalizedText, cQ as validateMindooDBAppDefinition } from './types-CsKw_niD.js';
1
+ import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppDragBindSourceOptions, b as MindooDBAppDragStartInput, c as MindooDBAppDragStartResult, d as MindooDBAppDragPreview, e as MindooDBAppBridge, f as MindooDBAppHostShortcutBinding, g as MindooDBAppHostShortcutAction, h as MindooDBAppHostingMode, i as MindooDBAppDefinition, j as MindooDBAppDatabase, k as MindooDBAppDocument, l as MindooDBAppRichTextSpan, m as MindooDBAppRichTextSnapshot, n as MindooDBAppCreateDocumentInput } from './types-CX-o1Xyq.js';
2
+ export { o as MINDOODB_APP_DEFINITION_FILE_NAME, p as MINDOODB_APP_DEFINITION_FORMAT, q as MINDOODB_APP_DEFINITION_PERMISSIONS, r as MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS, s as MINDOODB_APP_DEFINITION_VERSION, t as MINDOODB_APP_LISTING_DESCRIPTION_MAX, u as MINDOODB_APP_LISTING_MAX_SCREENSHOTS, v as MINDOODB_APP_LISTING_SUMMARY_MAX, w as MINDOODB_APP_SEAL_SIGNING_DOMAIN, x as MINDOODB_APP_WELL_KNOWN_DRAG_TYPES, y as MindooDBAppAccessDecision, z as MindooDBAppAccessTier, A as MindooDBAppAttachmentApi, B as MindooDBAppAttachmentChunk, C as MindooDBAppAttachmentInfo, D as MindooDBAppAttachmentPreviewOptions, E as MindooDBAppAttachmentPreviewSession, F as MindooDBAppAutomergeChangesPatch, G as MindooDBAppAutomergeChangesSince, H as MindooDBAppAutomergeGetOptions, I as MindooDBAppAutomergePatchResult, J as MindooDBAppAutomergeSnapshot, K as MindooDBAppBridgeBeforeCloseAck, L as MindooDBAppBridgeBeforeCloseMessage, N as MindooDBAppBridgeConnectMessage, O as MindooDBAppBridgeConnectOptions, P as MindooDBAppBridgeConnectedMessage, Q as MindooDBAppBridgeDragDropMessage, R as MindooDBAppBridgeDragLeaveMessage, S as MindooDBAppBridgeDragOverMessage, T as MindooDBAppBridgeErrorPayload, U as MindooDBAppBridgeHandshakeErrorMessage, V as MindooDBAppBridgeHostFocusChangedMessage, W as MindooDBAppBridgeLocaleChangedMessage, X as MindooDBAppBridgePortMessage, Y as MindooDBAppBridgeQueryResultMessage, Z as MindooDBAppBridgeRpcError, _ as MindooDBAppBridgeRpcMessage, $ as MindooDBAppBridgeRpcRequest, a0 as MindooDBAppBridgeRpcSuccess, a1 as MindooDBAppBridgeShortcutInvokedMessage, a2 as MindooDBAppBridgeShortcutsChangedMessage, a3 as MindooDBAppBridgeStreamAbortRequest, a4 as MindooDBAppBridgeStreamAck, a5 as MindooDBAppBridgeStreamCloseRequest, a6 as MindooDBAppBridgeStreamError, a7 as MindooDBAppBridgeStreamMessage, a8 as MindooDBAppBridgeStreamOpenResult, a9 as MindooDBAppBridgeStreamReadRequest, aa as MindooDBAppBridgeStreamWriteRequest, ab as MindooDBAppBridgeThemeChangedMessage, ac as MindooDBAppBridgeUiPreferencesChangedMessage, ad as MindooDBAppBridgeViewChangedMessage, ae as MindooDBAppBridgeViewportChangedMessage, af as MindooDBAppBridgeWorkspaceFocusRequestedMessage, ag as MindooDBAppCapability, ah as MindooDBAppConfiguredViewCategorizationStyle, ai as MindooDBAppConfiguredViewColumn, aj as MindooDBAppConfiguredViewColumnRole, ak as MindooDBAppConfiguredViewExpressionMode, al as MindooDBAppConfiguredViewFieldExpression, am as MindooDBAppConfiguredViewFilterDefinition, an as MindooDBAppConfiguredViewFilterMode, ao as MindooDBAppConfiguredViewFilterRule, ap as MindooDBAppConfiguredViewFormulaExpression, aq as MindooDBAppConfiguredViewFormulaFilterDefinition, ar as MindooDBAppConfiguredViewPreviewMode, as as MindooDBAppConfiguredViewRuleFilterDefinition, at as MindooDBAppConfiguredViewRuleMatchMode, au as MindooDBAppConfiguredViewRuleOperator, av as MindooDBAppConfiguredViewValueExpression, aw as MindooDBAppCreateKeyInfo, ax as MindooDBAppCreateViewInput, ay as MindooDBAppCreateViewNavigatorInput, az as MindooDBAppDatabaseInfo, aA as MindooDBAppDefinitionDatabase, aB as MindooDBAppDefinitionListing, aC as MindooDBAppDefinitionPermission, aD as MindooDBAppDefinitionRegistrationPermission, aE as MindooDBAppDefinitionScreenshot, aF as MindooDBAppDefinitionValidation, aG as MindooDBAppDirectoryApi, aH as MindooDBAppDirectoryEntry, aI as MindooDBAppDocumentApi, aJ as MindooDBAppDocumentHeadCursorResult, aK as MindooDBAppDocumentHistoryEntry, aL as MindooDBAppDocumentListQuery, aM as MindooDBAppDocumentListResult, aN as MindooDBAppDocumentQuery, aO as MindooDBAppDocumentRevisionId, aP as MindooDBAppDocumentScanPreset, aQ as MindooDBAppDocumentSummary, aR as MindooDBAppDragApi, aS as MindooDBAppDragCancelReason, aT as MindooDBAppDragDropEvent, aU as MindooDBAppDragEffect, aV as MindooDBAppDragHoverResult, aW as MindooDBAppDragOffer, aX as MindooDBAppDragOverEvent, aY as MindooDBAppDragPointer, aZ as MindooDBAppDragProfile, a_ as MindooDBAppEntryAttachmentRef, a$ as MindooDBAppEntryProvenance, b0 as MindooDBAppExtractDocumentTextInput, b1 as MindooDBAppExtractDocumentTextResult, b2 as MindooDBAppExtractTextFormat, b3 as MindooDBAppExtractionSetup, b4 as MindooDBAppFulltextSetup, b5 as MindooDBAppHistoricalDocument, b6 as MindooDBAppHistoryQuery, b7 as MindooDBAppHostTheme, b8 as MindooDBAppIdentityApi, b9 as MindooDBAppInaccessibleDocument, ba as MindooDBAppInaccessibleDocumentListQuery, bb as MindooDBAppInaccessibleDocumentListResult, bc as MindooDBAppJsonCounterIncrementPatch, bd as MindooDBAppJsonListDeletePatch, be as MindooDBAppJsonListInsertPatch, bf as MindooDBAppJsonPatch, bg as MindooDBAppJsonSetPatch, bh as MindooDBAppJsonTextMarkPatch, bi as MindooDBAppJsonTextSplicePatch, bj as MindooDBAppJsonTextUnmarkPatch, bk as MindooDBAppJsonUnsetPatch, bl as MindooDBAppLaunchContext, bm as MindooDBAppLiveQuerySubscription, bn as MindooDBAppLocalizedText, bo as MindooDBAppMenuAnchor, bp as MindooDBAppMenuApi, bq as MindooDBAppMenuCommandItem, br as MindooDBAppMenuDismissReason, bs as MindooDBAppMenuDismissedResult, bt as MindooDBAppMenuItem, bu as MindooDBAppMenuKind, bv as MindooDBAppMenuPlacement, bw as MindooDBAppMenuPointAnchor, bx as MindooDBAppMenuRect, by as MindooDBAppMenuRectAnchor, bz as MindooDBAppMenuSelectionResult, bA as MindooDBAppMenuSeparatorItem, bB as MindooDBAppNotificationSeverity, bC as MindooDBAppNotifyInput, bD as MindooDBAppNotifyResult, bE as MindooDBAppProposeAppInput, bF as MindooDBAppProposeAppResult, bG as MindooDBAppQueryCoverage, bH as MindooDBAppQueryInclude, bI as MindooDBAppQueryIncludeCardinality, bJ as MindooDBAppQueryIncludeSlots, bK as MindooDBAppQueryResult, bL as MindooDBAppQueryRow, bM as MindooDBAppQuerySortKey, bN as MindooDBAppQueryTextClause, bO as MindooDBAppReadableAttachmentStream, bP as MindooDBAppRecipientOptions, bQ as MindooDBAppResolvedViewDefinition, bR as MindooDBAppResolvedViewSource, bS as MindooDBAppRevisionPhase, bT as MindooDBAppRevisionVerification, bU as MindooDBAppRichTextBlockSpan, bV as MindooDBAppRichTextGetOptions, bW as MindooDBAppRichTextImmutableString, bX as MindooDBAppRichTextMarkRange, bY as MindooDBAppRichTextMaterializeValue, bZ as MindooDBAppRichTextPatch, b_ as MindooDBAppRichTextScalar, b$ as MindooDBAppRichTextSpliceStep, c0 as MindooDBAppRichTextStep, c1 as MindooDBAppRichTextStepPatch, c2 as MindooDBAppRichTextTextSpan, c3 as MindooDBAppRuntime, c4 as MindooDBAppScanAttachmentOptions, c5 as MindooDBAppScanAttachmentResult, c6 as MindooDBAppScopedDocId, c7 as MindooDBAppScopedDocumentSummary, c8 as MindooDBAppSealIntent, c9 as MindooDBAppSealedChannelOpenInput, ca as MindooDBAppSealedChannelOpenResult, cb as MindooDBAppSealedChannelRequestInput, cc as MindooDBAppSession, cd as MindooDBAppShowMenuInput, ce as MindooDBAppShowMenuResult, cf as MindooDBAppSignPreview, cg as MindooDBAppSignPreviewScope, ch as MindooDBAppSignStatementInput, ci as MindooDBAppSignStatementResult, cj as MindooDBAppStorageApi, ck as MindooDBAppSummarySetup, cl as MindooDBAppTextCursorOptions, cm as MindooDBAppTextCursorPosition, cn as MindooDBAppTextCursorPositionsResult, co as MindooDBAppTextCursorsResult, cp as MindooDBAppTextEdit, cq as MindooDBAppTextPatch, cr as MindooDBAppThemeMode, cs as MindooDBAppTimestampApi, ct as MindooDBAppTimestampInput, cu as MindooDBAppTimestampProvider, cv as MindooDBAppTimestampResult, cw as MindooDBAppUiPreferences, cx as MindooDBAppUpdateDocumentInput, cy as MindooDBAppViewCursorDocumentListResult, cz as MindooDBAppViewCursorResult, cA as MindooDBAppViewEntry, cB as MindooDBAppViewEntryKind, cC as MindooDBAppViewNavigator, cD as MindooDBAppViewNavigatorExpansionState, cE as MindooDBAppViewNavigatorOpenOptions, cF as MindooDBAppViewNavigatorPageOptions, cG as MindooDBAppViewNavigatorPageResult, cH as MindooDBAppViewNavigatorRangeQuery, cI as MindooDBAppViewNavigatorSelectionState, cJ as MindooDBAppViewUpdateStats, cK as MindooDBAppViewport, cL as MindooDBAppWellKnownDragType, cM as MindooDBAppWritableAttachmentStream, cN as resolveMindooDBAppDefinitionUrl, cO as resolveMindooDBAppListingAssetUrl, cP as resolveMindooDBAppLocalizedText, cQ as validateMindooDBAppDefinition } from './types-CX-o1Xyq.js';
3
3
  export { M as MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME, a as MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME, b as MINDOODB_APP_BUNDLE_MANIFEST_FORMAT, c as MINDOODB_APP_BUNDLE_MANIFEST_VERSION, d as MindooDBAppBundleArchive, e as MindooDBAppBundleFileEntry, f as MindooDBAppBundleHash, g as MindooDBAppBundleManifest, h as MindooDBAppBundleManifestValidation, i as buildMindooDBAppBundleContentHashInput, j as isMindooDBAppBundleHash, n as normalizeMindooDBAppBundlePath, v as validateMindooDBAppBundleManifest } from './appBundleManifest-B0xfpaXG.js';
4
4
  import { MindooDBAppBooleanExpression } from 'mindoodb-view-language';
5
5
  export { MindooDBAppBooleanExpression, MindooDBAppExpression, MindooDBAppFilterDefinition, MindooDBAppViewCategoryChildrenPageRequest, MindooDBAppViewColumn, MindooDBAppViewColumnRole, MindooDBAppViewDefinition, MindooDBAppViewExpansionState, MindooDBAppViewExpressionBase, MindooDBAppViewExpressionDatePart, MindooDBAppViewExpressionOperation, MindooDBAppViewFieldExpression, MindooDBAppViewIfExpression, MindooDBAppViewLetExpression, MindooDBAppViewLiteralExpression, MindooDBAppViewLookupByPath, MindooDBAppViewOperationExpression, MindooDBAppViewOriginExpression, MindooDBAppViewPageRequest, MindooDBAppViewPageResult, MindooDBAppViewRow, MindooDBAppViewSortDirection, MindooDBAppViewTotalMode, MindooDBAppViewValueExpressionRef, MindooDBAppViewVariableExpression, createViewLanguage } from 'mindoodb-view-language';
@@ -295,6 +295,64 @@ declare function buildHavenAppInstallUrl(appUrl: string, havenUrl?: string): str
295
295
  */
296
296
  declare function renderHavenAppLandingPage(options?: RenderHavenAppLandingPageOptions): Promise<HavenAppLandingPage>;
297
297
 
298
+ /**
299
+ * The Markdown subset a publisher may use in `listing.descriptionMarkdown`.
300
+ *
301
+ * The text comes from a file on the publisher's server and is shown before anyone has
302
+ * agreed to install the app, so it is parsed into a small tree here and rendered with
303
+ * `createElement`/`textContent` (or Vue render functions) — never as HTML. What the
304
+ * subset knows:
305
+ *
306
+ * - paragraphs (blank lines), headings (`#`), bullet (`-`, `*`, `+`) and numbered lists
307
+ * - `**bold**`, `__bold__`, `*italic*`, `_italic_`, `` `code` ``, `\` escapes
308
+ * - `[text](https://…)`, `[text](mailto:…)` and bare `https://…` links
309
+ *
310
+ * Anything else stays literal text. Links are limited to `https:` and `mailto:` by
311
+ * {@link sanitizeListingHref}; a link with any other target renders as its text only.
312
+ *
313
+ * DOM-free on purpose, so Haven, the landing page and build scripts can share it.
314
+ */
315
+ type ListingInline = {
316
+ type: "text";
317
+ text: string;
318
+ } | {
319
+ type: "code";
320
+ text: string;
321
+ } | {
322
+ type: "strong";
323
+ children: ListingInline[];
324
+ } | {
325
+ type: "em";
326
+ children: ListingInline[];
327
+ } | {
328
+ type: "link";
329
+ href: string;
330
+ children: ListingInline[];
331
+ };
332
+ type ListingBlock = {
333
+ type: "paragraph";
334
+ children: ListingInline[];
335
+ } | {
336
+ type: "heading";
337
+ children: ListingInline[];
338
+ } | {
339
+ type: "list";
340
+ ordered: boolean;
341
+ items: ListingInline[][];
342
+ };
343
+ /**
344
+ * `https:` and `mailto:` only. Embedded tab/LF/CR are stripped first, as browsers do,
345
+ * so `java\nscript:` cannot slip past the scheme check.
346
+ */
347
+ declare function sanitizeListingHref(raw: string): string | null;
348
+ /** Parses a description into blocks. Unknown syntax stays text; nothing is dropped. */
349
+ declare function parseListingMarkdown(markdown: string): ListingBlock[];
350
+ /**
351
+ * The same text for readers that only show plain paragraphs (`listing.description`):
352
+ * one paragraph per block and per list item, links as "text (url)".
353
+ */
354
+ declare function listingMarkdownToPlainText(markdown: string): string;
355
+
298
356
  interface InstallHavenStorageShimOptions {
299
357
  /**
300
358
  * Key prefixes whose values are written through to the host and restored on the next
@@ -501,4 +559,4 @@ declare function inheritDocumentEncryption(parent: Pick<MindooDBAppDocument, "de
501
559
  */
502
560
  declare function watchWordChunks(database: MindooDBAppDatabase, parentId: string, listener: (docIds: string[]) => void): Promise<() => Promise<void>>;
503
561
 
504
- export { type CreateMindooDBRichTextHandleOptions, type CreateMindooDBTextBufferOptions, DEFAULT_DRAG_LONG_PRESS_MS, DEFAULT_DRAG_POINTER_THRESHOLD_PX, DEFAULT_HAVEN_HOST_SHORTCUTS, DEFAULT_HAVEN_URL, type HavenAppLandingPage, type HavenStorageShimHandle, type InstallHavenStorageShimOptions, MINDOODB_APP_HOSTING_QUERY_PARAM, MINDOODB_APP_LAUNCH_ID_QUERY_PARAM, MINDOODB_APP_MAX_DRAG_PAYLOAD_BYTES, MINDOODB_APP_MAX_DRAG_PREVIEW_BYTES, MINDOODB_APP_MAX_DRAG_PREVIEW_EDGE, MINDOODB_APP_MAX_DRAG_TYPES, MINDOODB_APP_VALUE_TAG, MINIMAL_DRAG_PREVIEW_PNG, type MindooDBAppAtomicValue, MindooDBAppAttachmentPreviewMode, MindooDBAppBridge, type MindooDBAppCounterValue, MindooDBAppCreateDocumentInput, MindooDBAppDatabase, MindooDBAppDefinition, MindooDBAppDocument, MindooDBAppDragBindSourceOptions, MindooDBAppDragInputError, MindooDBAppDragPreview, MindooDBAppDragStartInput, MindooDBAppDragStartResult, MindooDBAppHostShortcutAction, MindooDBAppHostShortcutBinding, MindooDBAppHostingMode, MindooDBAppRichTextSnapshot, MindooDBAppRichTextSpan, type MindooDBAppTimestampValue, type MindooDBAppTypedValue, MindooDBAppValue, MindooDBRichTextHandle, type MindooDBRichTextHandleChangeListener, type MindooDBRichTextHandleFlushResult, MindooDBTextBuffer, type MindooDBTextBufferFlushResult, type RenderHavenAppLandingPageOptions, abbreviateCanonicalName, applyHostedDocumentOverscrollContain, bindDragSource, buildHavenAppInstallUrl, canPreviewAttachment, createMindooDBAppBridge, createMindooDBRichTextHandle, createMindooDBTextBuffer, expandAbbreviatedName, inheritDocumentEncryption, installHavenStorageShim, installHostShortcutCapture, isHostShortcutAction, isHostedBundleRuntime, isLaunchedByHaven, isMindooDBAppDragType, matchHostShortcut, normalizeDragAccepts, normalizeDragPointerInput, normalizeDragStartInput, queryDocuments, readMindooDBAppHostingMode, readMindooDBAppLaunchId, releaseMindooDBAppBridgeSessions, renderHavenAppLandingPage, resolveCurrentHavenAppUrl, sanitizeHostShortcutBindings, snapshotElementToPng, watchWordChunks };
562
+ export { type CreateMindooDBRichTextHandleOptions, type CreateMindooDBTextBufferOptions, DEFAULT_DRAG_LONG_PRESS_MS, DEFAULT_DRAG_POINTER_THRESHOLD_PX, DEFAULT_HAVEN_HOST_SHORTCUTS, DEFAULT_HAVEN_URL, type HavenAppLandingPage, type HavenStorageShimHandle, type InstallHavenStorageShimOptions, type ListingBlock, type ListingInline, MINDOODB_APP_HOSTING_QUERY_PARAM, MINDOODB_APP_LAUNCH_ID_QUERY_PARAM, MINDOODB_APP_MAX_DRAG_PAYLOAD_BYTES, MINDOODB_APP_MAX_DRAG_PREVIEW_BYTES, MINDOODB_APP_MAX_DRAG_PREVIEW_EDGE, MINDOODB_APP_MAX_DRAG_TYPES, MINDOODB_APP_VALUE_TAG, MINIMAL_DRAG_PREVIEW_PNG, type MindooDBAppAtomicValue, MindooDBAppAttachmentPreviewMode, MindooDBAppBridge, type MindooDBAppCounterValue, MindooDBAppCreateDocumentInput, MindooDBAppDatabase, MindooDBAppDefinition, MindooDBAppDocument, MindooDBAppDragBindSourceOptions, MindooDBAppDragInputError, MindooDBAppDragPreview, MindooDBAppDragStartInput, MindooDBAppDragStartResult, MindooDBAppHostShortcutAction, MindooDBAppHostShortcutBinding, MindooDBAppHostingMode, MindooDBAppRichTextSnapshot, MindooDBAppRichTextSpan, type MindooDBAppTimestampValue, type MindooDBAppTypedValue, MindooDBAppValue, MindooDBRichTextHandle, type MindooDBRichTextHandleChangeListener, type MindooDBRichTextHandleFlushResult, MindooDBTextBuffer, type MindooDBTextBufferFlushResult, type RenderHavenAppLandingPageOptions, abbreviateCanonicalName, applyHostedDocumentOverscrollContain, bindDragSource, buildHavenAppInstallUrl, canPreviewAttachment, createMindooDBAppBridge, createMindooDBRichTextHandle, createMindooDBTextBuffer, expandAbbreviatedName, inheritDocumentEncryption, installHavenStorageShim, installHostShortcutCapture, isHostShortcutAction, isHostedBundleRuntime, isLaunchedByHaven, isMindooDBAppDragType, listingMarkdownToPlainText, matchHostShortcut, normalizeDragAccepts, normalizeDragPointerInput, normalizeDragStartInput, parseListingMarkdown, queryDocuments, readMindooDBAppHostingMode, readMindooDBAppLaunchId, releaseMindooDBAppBridgeSessions, renderHavenAppLandingPage, resolveCurrentHavenAppUrl, sanitizeHostShortcutBindings, sanitizeListingHref, snapshotElementToPng, watchWordChunks };
package/dist/index.js CHANGED
@@ -286,6 +286,13 @@ function readListing(value, errors) {
286
286
  errors
287
287
  );
288
288
  if (description) listing.description = description;
289
+ const descriptionMarkdown = readLocalizedText(
290
+ value.descriptionMarkdown,
291
+ "App definition listing.descriptionMarkdown",
292
+ MINDOODB_APP_LISTING_DESCRIPTION_MAX,
293
+ errors
294
+ );
295
+ if (descriptionMarkdown) listing.descriptionMarkdown = descriptionMarkdown;
289
296
  const icon = readListingAssetPath(value.icon, "App definition listing.icon", errors);
290
297
  if (icon) listing.icon = icon;
291
298
  if (value.screenshots !== void 0 && value.screenshots !== null) {
@@ -575,6 +582,212 @@ function isHostedBundleRuntime(search) {
575
582
  return readMindooDBAppHostingMode(search) === "hosted";
576
583
  }
577
584
 
585
+ // src/listingMarkdown.ts
586
+ var MAX_INLINE_DEPTH = 4;
587
+ var HEADING = /^#{1,6}\s+(.*?)\s*#*\s*$/;
588
+ var BULLET = /^[-*+]\s+(.*)$/;
589
+ var ORDERED = /^\d{1,9}[.)]\s+(.*)$/;
590
+ var ESCAPABLE = new Set("\\`*_[]()#+-.!<>".split(""));
591
+ var BARE_URL_TRAILING_PUNCTUATION = /[.,;:!?)\]'"]+$/;
592
+ function sanitizeListingHref(raw) {
593
+ const cleaned = raw.replace(/[\t\n\r]/g, "").trim();
594
+ if (!cleaned) {
595
+ return null;
596
+ }
597
+ try {
598
+ const url = new URL(cleaned);
599
+ if (url.protocol === "https:") {
600
+ return url.toString();
601
+ }
602
+ if (url.protocol === "mailto:" && url.pathname) {
603
+ return url.toString();
604
+ }
605
+ } catch {
606
+ }
607
+ return null;
608
+ }
609
+ function isWordCharacter(char) {
610
+ return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
611
+ }
612
+ function pushText(nodes, text) {
613
+ if (!text) {
614
+ return;
615
+ }
616
+ const last = nodes[nodes.length - 1];
617
+ if (last?.type === "text") {
618
+ last.text += text;
619
+ } else {
620
+ nodes.push({ type: "text", text });
621
+ }
622
+ }
623
+ function findClosingDelimiter(text, from, delimiter) {
624
+ let index = text.indexOf(delimiter, from);
625
+ while (index !== -1) {
626
+ const before = text[index - 1];
627
+ const after = text[index + delimiter.length];
628
+ const closesAfterContent = index > from && before !== " " && before !== "\\";
629
+ const underscoreBoundary = delimiter[0] !== "_" || !isWordCharacter(after);
630
+ if (closesAfterContent && underscoreBoundary) {
631
+ return index;
632
+ }
633
+ index = text.indexOf(delimiter, index + 1);
634
+ }
635
+ return -1;
636
+ }
637
+ function findLinkTargetEnd(text, from) {
638
+ let open = 0;
639
+ for (let index = from; index < text.length; index += 1) {
640
+ const char = text[index];
641
+ if (char === "(") {
642
+ open += 1;
643
+ } else if (char === ")") {
644
+ if (open === 0) return index;
645
+ open -= 1;
646
+ } else if (char === " ") {
647
+ return -1;
648
+ }
649
+ }
650
+ return -1;
651
+ }
652
+ function parseInline(text, depth = 0) {
653
+ const nodes = [];
654
+ let index = 0;
655
+ while (index < text.length) {
656
+ const char = text[index];
657
+ if (char === "\\" && ESCAPABLE.has(text[index + 1] ?? "")) {
658
+ pushText(nodes, text[index + 1]);
659
+ index += 2;
660
+ continue;
661
+ }
662
+ if (char === "`") {
663
+ const end = text.indexOf("`", index + 1);
664
+ if (end > index + 1) {
665
+ nodes.push({ type: "code", text: text.slice(index + 1, end) });
666
+ index = end + 1;
667
+ continue;
668
+ }
669
+ }
670
+ if (depth < MAX_INLINE_DEPTH && (char === "*" || char === "_")) {
671
+ const strong = text[index + 1] === char;
672
+ const delimiter = strong ? char + char : char;
673
+ const next = text[index + delimiter.length];
674
+ const opens = next !== void 0 && next !== " " && (char !== "_" || !isWordCharacter(text[index - 1]));
675
+ if (opens) {
676
+ const end = findClosingDelimiter(text, index + delimiter.length, delimiter);
677
+ if (end !== -1) {
678
+ const children = parseInline(text.slice(index + delimiter.length, end), depth + 1);
679
+ nodes.push(strong ? { type: "strong", children } : { type: "em", children });
680
+ index = end + delimiter.length;
681
+ continue;
682
+ }
683
+ }
684
+ }
685
+ if (char === "[" && depth < MAX_INLINE_DEPTH) {
686
+ const labelEnd = text.indexOf("](", index + 1);
687
+ const urlEnd = labelEnd === -1 ? -1 : findLinkTargetEnd(text, labelEnd + 2);
688
+ if (labelEnd > index + 1 && urlEnd !== -1) {
689
+ const label = parseInline(text.slice(index + 1, labelEnd), depth + 1);
690
+ const href = sanitizeListingHref(text.slice(labelEnd + 2, urlEnd));
691
+ if (href) {
692
+ nodes.push({ type: "link", href, children: label });
693
+ } else {
694
+ nodes.push(...label);
695
+ }
696
+ index = urlEnd + 1;
697
+ continue;
698
+ }
699
+ }
700
+ if (char === "h" && text.startsWith("https://", index) && !isWordCharacter(text[index - 1])) {
701
+ let end = index;
702
+ while (end < text.length && !/\s/.test(text[end])) end += 1;
703
+ const candidate = text.slice(index, end).replace(BARE_URL_TRAILING_PUNCTUATION, "");
704
+ const href = sanitizeListingHref(candidate);
705
+ if (href) {
706
+ nodes.push({ type: "link", href, children: [{ type: "text", text: candidate }] });
707
+ index += candidate.length;
708
+ continue;
709
+ }
710
+ }
711
+ pushText(nodes, char);
712
+ index += 1;
713
+ }
714
+ return nodes;
715
+ }
716
+ function parseListingMarkdown(markdown) {
717
+ const blocks = [];
718
+ let paragraph = [];
719
+ let list = null;
720
+ const flushParagraph = () => {
721
+ if (paragraph.length) {
722
+ blocks.push({ type: "paragraph", children: parseInline(paragraph.join(" ")) });
723
+ paragraph = [];
724
+ }
725
+ };
726
+ const flushList = () => {
727
+ if (list) {
728
+ blocks.push({ type: "list", ordered: list.ordered, items: list.items.map((item) => parseInline(item)) });
729
+ list = null;
730
+ }
731
+ };
732
+ for (const rawLine of markdown.split(/\r?\n/)) {
733
+ const line = rawLine.trim();
734
+ if (!line) {
735
+ flushParagraph();
736
+ flushList();
737
+ continue;
738
+ }
739
+ const heading = HEADING.exec(line);
740
+ const bullet = BULLET.exec(line);
741
+ const ordered = bullet ? null : ORDERED.exec(line);
742
+ if (heading) {
743
+ flushParagraph();
744
+ flushList();
745
+ blocks.push({ type: "heading", children: parseInline(heading[1]) });
746
+ } else if (bullet || ordered) {
747
+ flushParagraph();
748
+ const isOrdered = Boolean(ordered);
749
+ if (list && list.ordered !== isOrdered) {
750
+ flushList();
751
+ }
752
+ list ??= { ordered: isOrdered, items: [] };
753
+ list.items.push((bullet ?? ordered)[1]);
754
+ } else if (list) {
755
+ list.items[list.items.length - 1] += ` ${line}`;
756
+ } else {
757
+ paragraph.push(line);
758
+ }
759
+ }
760
+ flushParagraph();
761
+ flushList();
762
+ return blocks;
763
+ }
764
+ function inlineToPlainText(nodes) {
765
+ return nodes.map((node) => {
766
+ if (node.type === "text" || node.type === "code") {
767
+ return node.text;
768
+ }
769
+ const label = inlineToPlainText(node.children);
770
+ if (node.type !== "link") {
771
+ return label;
772
+ }
773
+ const target = node.href.replace(/^mailto:/, "").replace(/^https:\/\//, "").replace(/\/$/, "");
774
+ return label.includes(target) ? label : `${label} (${node.href.replace(/^mailto:/, "")})`;
775
+ }).join("");
776
+ }
777
+ function listingMarkdownToPlainText(markdown) {
778
+ const paragraphs = [];
779
+ for (const block of parseListingMarkdown(markdown)) {
780
+ if (block.type === "list") {
781
+ block.items.forEach((item, index) => {
782
+ paragraphs.push(`${block.ordered ? `${index + 1}.` : "\u2022"} ${inlineToPlainText(item)}`);
783
+ });
784
+ } else {
785
+ paragraphs.push(inlineToPlainText(block.children));
786
+ }
787
+ }
788
+ return paragraphs.filter((paragraph) => paragraph.trim()).join("\n\n");
789
+ }
790
+
578
791
  // src/landing.ts
579
792
  var DEFAULT_HAVEN_URL = "https://haven.mindoodb.com";
580
793
  var APP_ICON_FALLBACKS = ["appicon.png", "appicon.webp", "appicon.svg"];
@@ -638,6 +851,43 @@ function el(tag, className, text) {
638
851
  if (text !== void 0) node.textContent = text;
639
852
  return node;
640
853
  }
854
+ function externalLink(href) {
855
+ const link = el("a");
856
+ link.href = href;
857
+ link.rel = "noopener noreferrer";
858
+ link.target = "_blank";
859
+ return link;
860
+ }
861
+ function appendInline(parent, nodes) {
862
+ for (const node of nodes) {
863
+ if (node.type === "text") {
864
+ parent.append(node.text);
865
+ } else if (node.type === "code") {
866
+ parent.append(el("code", void 0, node.text));
867
+ } else {
868
+ const child = node.type === "link" ? externalLink(node.href) : el(node.type);
869
+ appendInline(child, node.children);
870
+ parent.append(child);
871
+ }
872
+ }
873
+ }
874
+ function renderMarkdownDescription(block, markdown) {
875
+ for (const entry of parseListingMarkdown(markdown)) {
876
+ if (entry.type === "list") {
877
+ const list = el(entry.ordered ? "ol" : "ul");
878
+ for (const item of entry.items) {
879
+ const li = el("li");
880
+ appendInline(li, item);
881
+ list.append(li);
882
+ }
883
+ block.append(list);
884
+ } else {
885
+ const node = el(entry.type === "heading" ? "h2" : "p");
886
+ appendInline(node, entry.children);
887
+ block.append(node);
888
+ }
889
+ }
890
+ }
641
891
  var STYLE_ID = "mdb-landing-style";
642
892
  var STYLE = `
643
893
  .mdb-landing{--bg:#f4f6fb;--card:#fff;--text:#172033;--muted:#5b6478;--line:#e2e6ef;--accent:#1f3a8a;--accent-text:#fff;
@@ -657,7 +907,12 @@ var STYLE = `
657
907
  .mdb-landing__button:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
658
908
  .mdb-landing__hint{margin:0;max-width:420px;color:var(--muted);font-size:14px}
659
909
  .mdb-landing__description{margin:32px 0 0}
660
- .mdb-landing__description p{margin:0 0 12px}
910
+ .mdb-landing__description p,.mdb-landing__description ul,.mdb-landing__description ol{margin:0 0 12px}
911
+ .mdb-landing__description ul,.mdb-landing__description ol{padding-left:22px}
912
+ .mdb-landing__description li+li{margin-top:4px}
913
+ .mdb-landing__description h2{margin:20px 0 8px;font-size:17px}
914
+ .mdb-landing__description code{font:.92em ui-monospace,SFMono-Regular,Menlo,monospace;padding:1px 5px;border-radius:5px;background:var(--line)}
915
+ .mdb-landing__description a,.mdb-landing__meta a{color:var(--accent)}
661
916
  .mdb-landing__shots-title{margin:32px 0 12px;font-size:15px;color:var(--muted);font-weight:600}
662
917
  .mdb-landing__shots{display:grid;grid-auto-flow:column;grid-auto-columns:min(78%,420px);gap:16px;overflow-x:auto;padding-bottom:8px;scroll-snap-type:x mandatory}
663
918
  .mdb-landing__shot{margin:0;scroll-snap-align:start}
@@ -706,11 +961,14 @@ async function renderHavenAppLandingPage(options = {}) {
706
961
  }
707
962
  const titles = el("div");
708
963
  titles.append(el("p", "mdb-landing__eyebrow", strings.eyebrow), el("h1", "mdb-landing__title", label));
709
- const meta = [format(strings.from, { origin })];
964
+ const meta = el("p", "mdb-landing__meta");
710
965
  if (listing?.publisher) {
711
- meta.unshift(format(strings.by, { publisher: listing.publisher.name }));
966
+ const [before, after = ""] = strings.by.split("{publisher}");
967
+ const name = listing.publisher.url ? Object.assign(externalLink(listing.publisher.url), { textContent: listing.publisher.name }) : listing.publisher.name;
968
+ meta.append(before ?? "", name, after, " \xB7 ");
712
969
  }
713
- titles.append(el("p", "mdb-landing__meta", meta.join(" \xB7 ")));
970
+ meta.append(format(strings.from, { origin }));
971
+ titles.append(meta);
714
972
  head.append(titles);
715
973
  card.append(head);
716
974
  const summary = resolveMindooDBAppLocalizedText(listing?.summary, locale) || definition?.description || strings.fallbackSummary;
@@ -722,11 +980,16 @@ async function renderHavenAppLandingPage(options = {}) {
722
980
  button.dataset.testid = "haven-app-landing-install";
723
981
  cta.append(button, el("p", "mdb-landing__hint", format(strings.ctaHint, { label })));
724
982
  card.append(cta);
983
+ const descriptionMarkdown = resolveMindooDBAppLocalizedText(listing?.descriptionMarkdown, locale);
725
984
  const description = resolveMindooDBAppLocalizedText(listing?.description, locale);
726
- if (description) {
985
+ if (descriptionMarkdown || description) {
727
986
  const block = el("section", "mdb-landing__description");
728
- for (const paragraph of description.split(/\n\s*\n/)) {
729
- if (paragraph.trim()) block.append(el("p", void 0, paragraph.trim()));
987
+ if (descriptionMarkdown) {
988
+ renderMarkdownDescription(block, descriptionMarkdown);
989
+ } else {
990
+ for (const paragraph of description.split(/\n\s*\n/)) {
991
+ if (paragraph.trim()) block.append(el("p", void 0, paragraph.trim()));
992
+ }
730
993
  }
731
994
  card.append(block);
732
995
  }
@@ -1461,11 +1724,13 @@ export {
1461
1724
  isLaunchedByHaven,
1462
1725
  isMindooDBAppBundleHash,
1463
1726
  isMindooDBAppDragType,
1727
+ listingMarkdownToPlainText,
1464
1728
  matchHostShortcut,
1465
1729
  normalizeDragAccepts,
1466
1730
  normalizeDragPointerInput,
1467
1731
  normalizeDragStartInput,
1468
1732
  normalizeMindooDBAppBundlePath,
1733
+ parseListingMarkdown,
1469
1734
  queryDocuments,
1470
1735
  readMindooDBAppHostingMode,
1471
1736
  readMindooDBAppLaunchId,
@@ -1476,6 +1741,7 @@ export {
1476
1741
  resolveMindooDBAppListingAssetUrl,
1477
1742
  resolveMindooDBAppLocalizedText,
1478
1743
  sanitizeHostShortcutBindings,
1744
+ sanitizeListingHref,
1479
1745
  snapshotElementToPng,
1480
1746
  validateMindooDBAppBundleManifest,
1481
1747
  validateMindooDBAppDefinition,
@@ -1,4 +1,4 @@
1
- import { ay as MindooDBAppCreateViewNavigatorInput, cC as MindooDBAppViewNavigator, $ as MindooDBAppBridgeRpcRequest, b7 as MindooDBAppHostTheme, ag as MindooDBAppCapability, i as MindooDBAppDefinition, e as MindooDBAppBridge, cc as MindooDBAppSession, bE as MindooDBAppProposeAppInput, cK as MindooDBAppViewport, cw as MindooDBAppUiPreferences, bK as MindooDBAppQueryResult, cJ as MindooDBAppViewUpdateStats, X as MindooDBAppBridgePortMessage, bl as MindooDBAppLaunchContext, az as MindooDBAppDatabaseInfo, aI as MindooDBAppDocumentApi, cE as MindooDBAppViewNavigatorOpenOptions, A as MindooDBAppAttachmentApi, b8 as MindooDBAppIdentityApi, cs as MindooDBAppTimestampApi, aG as MindooDBAppDirectoryApi, k as MindooDBAppDocument, b4 as MindooDBAppFulltextSetup, b3 as MindooDBAppExtractionSetup, ck as MindooDBAppSummarySetup, bF as MindooDBAppProposeAppResult, c as MindooDBAppDragStartResult, aZ as MindooDBAppDragProfile } from '../types-CsKw_niD.js';
1
+ import { ay as MindooDBAppCreateViewNavigatorInput, cC as MindooDBAppViewNavigator, $ as MindooDBAppBridgeRpcRequest, b7 as MindooDBAppHostTheme, ag as MindooDBAppCapability, i as MindooDBAppDefinition, e as MindooDBAppBridge, cc as MindooDBAppSession, bE as MindooDBAppProposeAppInput, cK as MindooDBAppViewport, cw as MindooDBAppUiPreferences, bK as MindooDBAppQueryResult, cJ as MindooDBAppViewUpdateStats, X as MindooDBAppBridgePortMessage, bl as MindooDBAppLaunchContext, az as MindooDBAppDatabaseInfo, aI as MindooDBAppDocumentApi, cE as MindooDBAppViewNavigatorOpenOptions, A as MindooDBAppAttachmentApi, b8 as MindooDBAppIdentityApi, cs as MindooDBAppTimestampApi, aG as MindooDBAppDirectoryApi, k as MindooDBAppDocument, b4 as MindooDBAppFulltextSetup, b3 as MindooDBAppExtractionSetup, ck as MindooDBAppSummarySetup, bF as MindooDBAppProposeAppResult, c as MindooDBAppDragStartResult, aZ as MindooDBAppDragProfile } from '../types-CX-o1Xyq.js';
2
2
  import * as mindoodb from 'mindoodb';
3
3
  import 'mindoodb-view-language';
4
4
 
@@ -118,6 +118,13 @@ interface MindooDBAppDefinitionListing {
118
118
  summary?: MindooDBAppLocalizedText;
119
119
  /** Longer plain text. Blank lines separate paragraphs; no markup is rendered. */
120
120
  description?: MindooDBAppLocalizedText;
121
+ /**
122
+ * The same description with formatting: the Markdown subset of `listingMarkdown.ts`
123
+ * (paragraphs, headings, lists, bold/italic, code, `https:`/`mailto:` links).
124
+ * Readers that know this field show it instead of `description`; older ones ignore it
125
+ * and keep showing `description`, so set both when older Haven versions matter.
126
+ */
127
+ descriptionMarkdown?: MindooDBAppLocalizedText;
121
128
  /** Square icon, path relative to the app origin or an absolute `https:` URL. */
122
129
  icon?: string;
123
130
  screenshots?: MindooDBAppDefinitionScreenshot[];
@@ -26,22 +26,35 @@ interface HavenBundleOptions {
26
26
  }
27
27
  interface ResolvedViteConfig {
28
28
  root: string;
29
+ command?: "build" | "serve";
29
30
  build: {
30
31
  outDir: string;
31
32
  };
33
+ server?: {
34
+ host?: string | boolean;
35
+ port?: number;
36
+ };
37
+ }
38
+ interface ShareHtmlContext {
39
+ filename?: string;
40
+ server?: {
41
+ resolvedUrls?: {
42
+ local?: string[];
43
+ } | null;
44
+ };
32
45
  }
33
46
  /** Structural subset of Vite's `Plugin`, so the SDK does not need to depend on Vite. */
34
47
  interface HavenBundleVitePlugin {
35
48
  name: string;
36
- apply: "build";
49
+ /**
50
+ * Runs in dev and in build. The zip is still build-only; dev needs the same plugin so
51
+ * `index.html` carries the share tags while `vite dev` is serving it.
52
+ */
37
53
  configResolved: (config: ResolvedViteConfig) => void;
54
+ transformIndexHtml: (html: string, ctx: ShareHtmlContext) => Promise<string>;
38
55
  writeBundle: () => Promise<void>;
39
56
  closeBundle: () => Promise<void>;
40
57
  }
41
- /**
42
- * Emits `haven-bundle.json` and `haven-bundle.zip` next to the build output so a MindooDB
43
- * host can install and update the app from a URL.
44
- */
45
58
  declare function havenBundle(options?: HavenBundleOptions): HavenBundleVitePlugin;
46
59
 
47
60
  export { type HavenBundleOptions, type HavenBundleVitePlugin, havenBundle };
@@ -11,6 +11,127 @@ import { createHash } from "crypto";
11
11
  import { access, readdir, readFile, writeFile } from "fs/promises";
12
12
  import path from "path";
13
13
 
14
+ // src/shareMeta.ts
15
+ var SUMMARY_CARD = "summary";
16
+ function escapeText(value) {
17
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
18
+ }
19
+ function escapeAttr(value) {
20
+ return escapeText(value).replace(/"/g, "&quot;").replace(/[\t\n\r]/g, " ");
21
+ }
22
+ function meta(attr, key, content) {
23
+ return `<meta ${attr}="${key}" content="${escapeAttr(content)}">`;
24
+ }
25
+ function localized(value) {
26
+ if (typeof value === "string") {
27
+ return value.trim();
28
+ }
29
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
30
+ return "";
31
+ }
32
+ const record = value;
33
+ if (typeof record.en === "string" && record.en.trim()) {
34
+ return record.en.trim();
35
+ }
36
+ for (const text of Object.values(record)) {
37
+ if (typeof text === "string" && text.trim()) {
38
+ return text.trim();
39
+ }
40
+ }
41
+ return "";
42
+ }
43
+ function isRecord(value) {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+ function havenAppPublicUrl(raw) {
47
+ if (!isRecord(raw)) {
48
+ return void 0;
49
+ }
50
+ return havenAppSharePageUrl(raw.publicUrl);
51
+ }
52
+ function havenAppSharePageUrl(value) {
53
+ if (typeof value !== "string" || !value.trim()) {
54
+ return void 0;
55
+ }
56
+ try {
57
+ const url = new URL(value.trim());
58
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
59
+ return void 0;
60
+ }
61
+ return url.origin + "/";
62
+ } catch {
63
+ return void 0;
64
+ }
65
+ }
66
+ function absoluteRasterUrl(asset, pageUrl) {
67
+ if (/\.svg($|\?)/i.test(asset)) {
68
+ return void 0;
69
+ }
70
+ try {
71
+ const url = new URL(asset, pageUrl);
72
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
73
+ return void 0;
74
+ }
75
+ return url.href;
76
+ } catch {
77
+ return void 0;
78
+ }
79
+ }
80
+ function havenAppShareMeta(raw, pageUrl) {
81
+ const page = havenAppSharePageUrl(pageUrl);
82
+ if (!isRecord(raw) || !page) {
83
+ return void 0;
84
+ }
85
+ const title = typeof raw.label === "string" ? raw.label.trim() : "";
86
+ if (!title) {
87
+ return void 0;
88
+ }
89
+ const listing = isRecord(raw.listing) ? raw.listing : void 0;
90
+ const summary = localized(listing?.summary);
91
+ const descriptionText = localized(listing?.description);
92
+ const description = summary || descriptionText.split(/\n\s*\n/)[0]?.trim() || (typeof raw.description === "string" ? raw.description.trim() : "");
93
+ const icon = typeof listing?.icon === "string" ? listing.icon.trim() : "";
94
+ const imageUrl = icon ? absoluteRasterUrl(icon, page) : void 0;
95
+ return { title, description, pageUrl: page, ...imageUrl ? { imageUrl } : {} };
96
+ }
97
+ function renderHavenAppShareMeta(metaData) {
98
+ const lines = [
99
+ "<!-- mindoodb-share -->",
100
+ meta("name", "description", metaData.description),
101
+ meta("property", "og:type", "website"),
102
+ meta("property", "og:title", metaData.title),
103
+ meta("property", "og:description", metaData.description),
104
+ meta("property", "og:url", metaData.pageUrl)
105
+ ];
106
+ if (metaData.imageUrl) {
107
+ lines.push(meta("property", "og:image", metaData.imageUrl), meta("property", "og:image:alt", metaData.title));
108
+ }
109
+ lines.push(
110
+ meta("name", "twitter:card", SUMMARY_CARD),
111
+ meta("name", "twitter:title", metaData.title),
112
+ meta("name", "twitter:description", metaData.description)
113
+ );
114
+ if (metaData.imageUrl) {
115
+ lines.push(meta("name", "twitter:image", metaData.imageUrl));
116
+ }
117
+ lines.push("<!-- /mindoodb-share -->");
118
+ return lines.filter((line) => !line.includes('content=""')).join("\n");
119
+ }
120
+ var TITLE_PATTERN = /<title>[^<]*<\/title>/i;
121
+ function injectHavenAppShareMeta(html, metaData) {
122
+ if (html.includes("<!-- mindoodb-share -->")) {
123
+ return html;
124
+ }
125
+ const block = renderHavenAppShareMeta(metaData);
126
+ const titled = TITLE_PATTERN.test(html) ? html.replace(TITLE_PATTERN, `<title>${escapeText(metaData.title)}</title>`) : html;
127
+ const headEnd = titled.search(/<\/head>/i);
128
+ if (headEnd === -1) {
129
+ return titled;
130
+ }
131
+ return `${titled.slice(0, headEnd)}${block}
132
+ ${titled.slice(headEnd)}`;
133
+ }
134
+
14
135
  // src/vite/zip.ts
15
136
  import { deflateRawSync } from "zlib";
16
137
  var CRC32_TABLE = (() => {
@@ -229,11 +350,26 @@ async function readPackageMetadata(root) {
229
350
  return { name: void 0, version: void 0 };
230
351
  }
231
352
  }
353
+ function devPageUrl(config, ctx) {
354
+ const local = ctx.server?.resolvedUrls?.local ?? [];
355
+ const fromServer = havenAppSharePageUrl(local.find((url) => url.includes("127.0.0.1")) ?? local[0]);
356
+ if (fromServer) {
357
+ return fromServer;
358
+ }
359
+ const port = config.server?.port;
360
+ if (!port) {
361
+ return void 0;
362
+ }
363
+ const host = config.server?.host;
364
+ const hostname = !host || host === true || host === "0.0.0.0" ? "127.0.0.1" : host;
365
+ return havenAppSharePageUrl(`http://${hostname}:${port}/`);
366
+ }
232
367
  function havenBundle(options = {}) {
233
368
  let resolvedConfig = null;
234
369
  let emitted = false;
370
+ let isBuild = true;
235
371
  async function emitBundle() {
236
- if (emitted) {
372
+ if (!isBuild || emitted) {
237
373
  return;
238
374
  }
239
375
  if (!resolvedConfig) {
@@ -296,9 +432,27 @@ function havenBundle(options = {}) {
296
432
  }
297
433
  return {
298
434
  name: "haven-bundle",
299
- apply: "build",
300
435
  configResolved(config) {
301
436
  resolvedConfig = config;
437
+ isBuild = config.command !== "serve";
438
+ },
439
+ async transformIndexHtml(html, ctx) {
440
+ if (!resolvedConfig || ctx.filename?.includes("__haven-test")) {
441
+ return html;
442
+ }
443
+ let raw;
444
+ try {
445
+ raw = JSON.parse(await readFile(path.join(resolvedConfig.root, "public", "haven-app.json"), "utf8"));
446
+ } catch {
447
+ return html;
448
+ }
449
+ const baked = havenAppPublicUrl(raw);
450
+ const pageUrl = resolvedConfig.command === "serve" ? devPageUrl(resolvedConfig, ctx) ?? baked : baked;
451
+ if (!pageUrl) {
452
+ return html;
453
+ }
454
+ const meta2 = havenAppShareMeta(raw, pageUrl);
455
+ return meta2 ? injectHavenAppShareMeta(html, meta2) : html;
302
456
  },
303
457
  async writeBundle() {
304
458
  await emitBundle();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindoodb-app-sdk",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
4
4
  "engines": {
5
5
  "node": ">=22.13.0"
6
6
  },