docspan 0.4.0__tar.gz → 0.6.0__tar.gz

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.
Files changed (267) hide show
  1. {docspan-0.4.0 → docspan-0.6.0}/.github/workflows/publish.yml +6 -7
  2. docspan-0.6.0/.github/workflows/release-please.yml +39 -0
  3. {docspan-0.4.0 → docspan-0.6.0}/.gitignore +3 -0
  4. docspan-0.6.0/.mypy-error-baseline +1 -0
  5. docspan-0.6.0/.release-please-manifest.json +3 -0
  6. {docspan-0.4.0 → docspan-0.6.0}/CHANGELOG.md +62 -3
  7. {docspan-0.4.0 → docspan-0.6.0}/PKG-INFO +37 -2
  8. {docspan-0.4.0 → docspan-0.6.0}/README.md +36 -1
  9. {docspan-0.4.0 → docspan-0.6.0}/docs/backends/google-docs.md +4 -1
  10. {docspan-0.4.0 → docspan-0.6.0}/docs/install.md +4 -2
  11. docspan-0.6.0/project_plans/gdocs-native-blockquotes/decisions/ADR-001-native-blockquote-indent-and-border-styling.md +38 -0
  12. docspan-0.6.0/project_plans/gdocs-native-blockquotes/design/ux.md +353 -0
  13. docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/adversarial-review.md +13 -0
  14. docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/architecture-review.md +30 -0
  15. docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/epic-0-spike-findings.md +144 -0
  16. docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/plan.md +373 -0
  17. docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/pre-mortem.md +17 -0
  18. docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/validation.md +88 -0
  19. docspan-0.6.0/project_plans/gdocs-native-blockquotes/requirements.md +91 -0
  20. docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/architecture.md +207 -0
  21. docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/build-vs-buy.md +36 -0
  22. docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/features.md +76 -0
  23. docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/pitfalls.md +204 -0
  24. docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/stack.md +65 -0
  25. docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/ux.md +160 -0
  26. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/design/ux.md +297 -0
  27. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/adversarial-review.md +30 -0
  28. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/architecture-review.md +40 -0
  29. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/plan.md +564 -0
  30. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/pre-mortem.md +16 -0
  31. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/validation.md +79 -0
  32. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/requirements.md +70 -0
  33. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/architecture.md +244 -0
  34. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/build-vs-buy.md +133 -0
  35. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/features.md +155 -0
  36. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/pitfalls.md +271 -0
  37. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/stack.md +190 -0
  38. docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/ux.md +147 -0
  39. docspan-0.6.0/project_plans/gdocs-sectioned-sync/decisions/ADR-001-manifest-yaml-sidecar-keyed-by-heading-id.md +24 -0
  40. docspan-0.6.0/project_plans/gdocs-sectioned-sync/decisions/ADR-002-reorder-as-in-place-move.md +25 -0
  41. docspan-0.6.0/project_plans/gdocs-sectioned-sync/decisions/ADR-003-sectioned-pull-always-structural-path.md +19 -0
  42. docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/adversarial-review.md +28 -0
  43. docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/architecture-review.md +31 -0
  44. docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/plan.md +296 -0
  45. docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/pre-mortem.md +16 -0
  46. docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/validation.md +62 -0
  47. docspan-0.6.0/project_plans/gdocs-sectioned-sync/requirements.md +80 -0
  48. docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/architecture.md +216 -0
  49. docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/build-vs-buy.md +64 -0
  50. docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/features.md +257 -0
  51. docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/pitfalls.md +278 -0
  52. docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/stack.md +54 -0
  53. docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/ux.md +146 -0
  54. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/base.py +30 -0
  55. docspan-0.6.0/src/docspan/backends/confluence/anchors.py +111 -0
  56. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/backend.py +28 -3
  57. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/auth.py +24 -14
  58. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/backend.py +870 -74
  59. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/comments.py +83 -2
  60. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/docs_request_builder.py +475 -18
  61. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/docs_structure_parser.py +291 -7
  62. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/image_source.py +54 -6
  63. docspan-0.6.0/src/docspan/backends/google_docs/manifest.py +193 -0
  64. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/markdown_to_paragraph_parser.py +57 -52
  65. docspan-0.6.0/src/docspan/backends/google_docs/mermaid_appendix.py +174 -0
  66. docspan-0.6.0/src/docspan/backends/google_docs/mermaid_cache_sidecar.py +99 -0
  67. docspan-0.6.0/src/docspan/backends/google_docs/mermaid_renderer.py +256 -0
  68. docspan-0.6.0/src/docspan/backends/google_docs/migration.py +989 -0
  69. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/nodes_to_markdown.py +145 -6
  70. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/onboarding.py +4 -3
  71. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/projection.py +12 -1
  72. docspan-0.6.0/src/docspan/backends/google_docs/pulled_image_recovery.py +239 -0
  73. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/push_preview.py +85 -13
  74. docspan-0.6.0/src/docspan/backends/google_docs/section_splitter.py +194 -0
  75. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/cli/main.py +368 -12
  76. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/config.py +103 -7
  77. docspan-0.6.0/src/docspan/core/atomic_dir.py +82 -0
  78. docspan-0.6.0/src/docspan/core/orchestrator.py +877 -0
  79. docspan-0.6.0/src/docspan/core/paths.py +38 -0
  80. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/xdg.py +10 -0
  81. docspan-0.6.0/src/docspan/style_guide.py +81 -0
  82. docspan-0.6.0/tests/fixtures/blockquote_border_marker_spike.json +52 -0
  83. docspan-0.6.0/tests/test_atomic_dir.py +108 -0
  84. {docspan-0.4.0 → docspan-0.6.0}/tests/test_cli.py +216 -4
  85. {docspan-0.4.0 → docspan-0.6.0}/tests/test_code_block_granularity.py +81 -22
  86. {docspan-0.4.0 → docspan-0.6.0}/tests/test_config.py +137 -0
  87. docspan-0.6.0/tests/test_confluence_anchors.py +175 -0
  88. {docspan-0.4.0 → docspan-0.6.0}/tests/test_confluence_backend.py +35 -0
  89. docspan-0.6.0/tests/test_confluence_push_dead_anchors.py +90 -0
  90. {docspan-0.4.0 → docspan-0.6.0}/tests/test_docs_request_builder.py +203 -0
  91. {docspan-0.4.0 → docspan-0.6.0}/tests/test_docs_structure_parser.py +176 -2
  92. {docspan-0.4.0 → docspan-0.6.0}/tests/test_gdocs_images.py +28 -1
  93. docspan-0.6.0/tests/test_gdocs_mermaid.py +309 -0
  94. {docspan-0.4.0 → docspan-0.6.0}/tests/test_gdocs_push_pipeline.py +243 -1
  95. {docspan-0.4.0 → docspan-0.6.0}/tests/test_gdocs_tables_and_styles.py +28 -3
  96. {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_comments.py +107 -1
  97. {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_docs_backend.py +1947 -37
  98. {docspan-0.4.0 → docspan-0.6.0}/tests/test_heading_anchors.py +79 -0
  99. {docspan-0.4.0 → docspan-0.6.0}/tests/test_heading_identity.py +60 -0
  100. docspan-0.6.0/tests/test_lint.py +16 -0
  101. docspan-0.6.0/tests/test_manifest.py +115 -0
  102. {docspan-0.4.0 → docspan-0.6.0}/tests/test_markdown_to_paragraph_parser.py +90 -7
  103. docspan-0.6.0/tests/test_mermaid_appendix.py +95 -0
  104. docspan-0.6.0/tests/test_mermaid_cache_sidecar.py +115 -0
  105. docspan-0.6.0/tests/test_migrate_sectioned_cli.py +590 -0
  106. docspan-0.6.0/tests/test_migration.py +827 -0
  107. {docspan-0.4.0 → docspan-0.6.0}/tests/test_nodes_to_markdown.py +17 -5
  108. docspan-0.6.0/tests/test_orchestrator.py +948 -0
  109. docspan-0.6.0/tests/test_paths_data_uri_guard.py +51 -0
  110. docspan-0.6.0/tests/test_pulled_image_recovery.py +300 -0
  111. {docspan-0.4.0 → docspan-0.6.0}/tests/test_push_preview.py +135 -22
  112. docspan-0.6.0/tests/test_section_splitter.py +185 -0
  113. {docspan-0.4.0 → docspan-0.6.0}/tests/test_state.py +27 -0
  114. docspan-0.6.0/tests/test_style_guide.py +13 -0
  115. {docspan-0.4.0 → docspan-0.6.0}/tests/test_tabs.py +123 -0
  116. docspan-0.4.0/.github/workflows/release-please.yml +0 -18
  117. docspan-0.4.0/.mypy-error-baseline +0 -1
  118. docspan-0.4.0/.release-please-manifest.json +0 -3
  119. docspan-0.4.0/src/docspan/backends/google_docs/mermaid_renderer.py +0 -100
  120. docspan-0.4.0/src/docspan/core/orchestrator.py +0 -360
  121. docspan-0.4.0/src/docspan/core/paths.py +0 -8
  122. docspan-0.4.0/tests/test_gdocs_mermaid.py +0 -119
  123. docspan-0.4.0/tests/test_orchestrator.py +0 -336
  124. {docspan-0.4.0 → docspan-0.6.0}/.github/workflows/ci.yml +0 -0
  125. {docspan-0.4.0 → docspan-0.6.0}/CONTRIBUTING.md +0 -0
  126. {docspan-0.4.0 → docspan-0.6.0}/Procfile +0 -0
  127. {docspan-0.4.0 → docspan-0.6.0}/RAILWAY_SETUP.md +0 -0
  128. {docspan-0.4.0 → docspan-0.6.0}/doc.md +0 -0
  129. {docspan-0.4.0 → docspan-0.6.0}/docs/backends/confluence.md +0 -0
  130. {docspan-0.4.0 → docspan-0.6.0}/docs/commands.md +0 -0
  131. {docspan-0.4.0 → docspan-0.6.0}/docs/configuration.md +0 -0
  132. {docspan-0.4.0 → docspan-0.6.0}/docs/contributing.md +0 -0
  133. {docspan-0.4.0 → docspan-0.6.0}/docs/index.md +0 -0
  134. {docspan-0.4.0 → docspan-0.6.0}/docspan.yaml.example +0 -0
  135. {docspan-0.4.0 → docspan-0.6.0}/markgate.yaml.example +0 -0
  136. {docspan-0.4.0 → docspan-0.6.0}/mkdocs.yml +0 -0
  137. {docspan-0.4.0 → docspan-0.6.0}/modules/__init__.py +0 -0
  138. {docspan-0.4.0 → docspan-0.6.0}/modules/auth.py +0 -0
  139. {docspan-0.4.0 → docspan-0.6.0}/modules/conflict_handler.py +0 -0
  140. {docspan-0.4.0 → docspan-0.6.0}/modules/converter.py +0 -0
  141. {docspan-0.4.0 → docspan-0.6.0}/modules/gdrive_client.py +0 -0
  142. {docspan-0.4.0 → docspan-0.6.0}/modules/sync_engine.py +0 -0
  143. {docspan-0.4.0 → docspan-0.6.0}/project_plans/bidirectional-comments/plan.md +0 -0
  144. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/adversarial-review.md +0 -0
  145. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/plan.md +0 -0
  146. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/release-checklist.md +0 -0
  147. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/validation.md +0 -0
  148. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/requirements.md +0 -0
  149. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/architecture.md +0 -0
  150. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/features.md +0 -0
  151. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/google-docs-push.md +0 -0
  152. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/pitfalls.md +0 -0
  153. {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/stack.md +0 -0
  154. {docspan-0.4.0 → docspan-0.6.0}/project_plans/gdocs-tables-inline-styles/plan.md +0 -0
  155. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/decisions/ADR-001-merge3-dependency.md +0 -0
  156. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/decisions/ADR-002-base-content-sidecar-store.md +0 -0
  157. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/implementation/adversarial-review.md +0 -0
  158. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/implementation/plan.md +0 -0
  159. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/implementation/validation.md +0 -0
  160. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/requirements.md +0 -0
  161. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/architecture.md +0 -0
  162. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/features.md +0 -0
  163. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/pitfalls.md +0 -0
  164. {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/stack.md +0 -0
  165. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/decisions/ADR-001-checklist-state-as-literal-text.md +0 -0
  166. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/decisions/ADR-002-comment-risk-flagging-not-anchor-preservation.md +0 -0
  167. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/decisions/ADR-003-no-comment-anchor-migration.md +0 -0
  168. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/feature-gap-report.md +0 -0
  169. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/adversarial-review.md +0 -0
  170. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/architecture-review.md +0 -0
  171. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/plan.md +0 -0
  172. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/pre-mortem.md +0 -0
  173. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/validation.md +0 -0
  174. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/requirements.md +0 -0
  175. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/architecture.md +0 -0
  176. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/build-vs-buy.md +0 -0
  177. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/features.md +0 -0
  178. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/pitfalls.md +0 -0
  179. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/stack.md +0 -0
  180. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/ux.md +0 -0
  181. {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/workflow-runbook.md +0 -0
  182. {docspan-0.4.0 → docspan-0.6.0}/pyproject.toml +0 -0
  183. {docspan-0.4.0 → docspan-0.6.0}/release-please-config.json +0 -0
  184. {docspan-0.4.0 → docspan-0.6.0}/requirements.txt +0 -0
  185. {docspan-0.4.0 → docspan-0.6.0}/runtime.txt +0 -0
  186. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/__init__.py +0 -0
  187. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/__main__.py +0 -0
  188. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/__init__.py +0 -0
  189. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/__init__.py +0 -0
  190. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/__init__.py +0 -0
  191. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/comparator.py +0 -0
  192. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/converter.py +0 -0
  193. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/converters.py +0 -0
  194. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/interfaces.py +0 -0
  195. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/nodes.py +0 -0
  196. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/parser.py +0 -0
  197. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/validators.py +0 -0
  198. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/visitors.py +0 -0
  199. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/client.py +0 -0
  200. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/__init__.py +0 -0
  201. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/loader.py +0 -0
  202. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/models.py +0 -0
  203. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/validation.py +0 -0
  204. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/__init__.py +0 -0
  205. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/ast.py +0 -0
  206. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/__init__.py +0 -0
  207. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/frontmatter.py +0 -0
  208. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/mermaid.py +0 -0
  209. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/wikilinks.py +0 -0
  210. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/inline_parser.py +0 -0
  211. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/parser.py +0 -0
  212. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/__init__.py +0 -0
  213. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/markdown_file.py +0 -0
  214. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/page.py +0 -0
  215. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/path_utils.py +0 -0
  216. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/results.py +0 -0
  217. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/sync_status.py +0 -0
  218. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/__init__.py +0 -0
  219. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/__init__.py +0 -0
  220. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/attachment_client.py +0 -0
  221. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/base_client.py +0 -0
  222. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/client.py +0 -0
  223. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/comment_client.py +0 -0
  224. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/crawler.py +0 -0
  225. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/label_client.py +0 -0
  226. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/page_client.py +0 -0
  227. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/space_client.py +0 -0
  228. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/url_parser.py +0 -0
  229. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/__init__.py +0 -0
  230. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/checkbox_state.py +0 -0
  231. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/client.py +0 -0
  232. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/converter.py +0 -0
  233. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/cross_doc_links.py +0 -0
  234. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/heading_anchors.py +0 -0
  235. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/registry.py +0 -0
  236. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/tabs.py +0 -0
  237. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/cli/__init__.py +0 -0
  238. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/__init__.py +0 -0
  239. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/merge.py +0 -0
  240. {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/state.py +0 -0
  241. {docspan-0.4.0 → docspan-0.6.0}/sync.py +0 -0
  242. {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/README.md +0 -0
  243. {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/main.tf +0 -0
  244. {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/outputs.tf +0 -0
  245. {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/variables.tf +0 -0
  246. {docspan-0.4.0 → docspan-0.6.0}/terraform/main.tf +0 -0
  247. {docspan-0.4.0 → docspan-0.6.0}/terraform/variables.tf +0 -0
  248. {docspan-0.4.0 → docspan-0.6.0}/tests/__init__.py +0 -0
  249. {docspan-0.4.0 → docspan-0.6.0}/tests/conftest.py +0 -0
  250. {docspan-0.4.0 → docspan-0.6.0}/tests/fixtures/github_slugger_vectors.json +0 -0
  251. {docspan-0.4.0 → docspan-0.6.0}/tests/test_checkbox_state.py +0 -0
  252. {docspan-0.4.0 → docspan-0.6.0}/tests/test_conflict_resolution.py +0 -0
  253. {docspan-0.4.0 → docspan-0.6.0}/tests/test_confluence_mermaid_push_pipeline.py +0 -0
  254. {docspan-0.4.0 → docspan-0.6.0}/tests/test_content_key_pooling_performance.py +0 -0
  255. {docspan-0.4.0 → docspan-0.6.0}/tests/test_converter.py +0 -0
  256. {docspan-0.4.0 → docspan-0.6.0}/tests/test_cross_doc_link_issues.py +0 -0
  257. {docspan-0.4.0 → docspan-0.6.0}/tests/test_cross_doc_links.py +0 -0
  258. {docspan-0.4.0 → docspan-0.6.0}/tests/test_cross_doc_links_backend.py +0 -0
  259. {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_oauth.py +0 -0
  260. {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_onboarding.py +0 -0
  261. {docspan-0.4.0 → docspan-0.6.0}/tests/test_merge.py +0 -0
  262. {docspan-0.4.0 → docspan-0.6.0}/tests/test_registry.py +0 -0
  263. {docspan-0.4.0 → docspan-0.6.0}/tests/test_restyle_destruction_rate.py +0 -0
  264. {docspan-0.4.0 → docspan-0.6.0}/tests/test_span_trailing_newline.py +0 -0
  265. {docspan-0.4.0 → docspan-0.6.0}/tests/test_table_cell_spans.py +0 -0
  266. {docspan-0.4.0 → docspan-0.6.0}/tests/test_xdg_central_config.py +0 -0
  267. {docspan-0.4.0 → docspan-0.6.0}/uv.lock +0 -0
@@ -3,6 +3,11 @@ name: Publish to PyPI
3
3
  on:
4
4
  release:
5
5
  types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ ref:
9
+ description: "Git tag to build and publish (e.g. docspan-v0.4.0)"
10
+ required: true
6
11
 
7
12
  jobs:
8
13
  build:
@@ -10,19 +15,16 @@ jobs:
10
15
  steps:
11
16
  - uses: actions/checkout@v4
12
17
  with:
18
+ ref: ${{ inputs.ref || github.ref }}
13
19
  fetch-depth: 0 # needed for hatch-vcs version from git tags
14
-
15
20
  - name: Install uv
16
21
  uses: astral-sh/setup-uv@v4
17
-
18
22
  - name: Build package
19
23
  run: uv build
20
-
21
24
  - uses: actions/upload-artifact@v4.6.2
22
25
  with:
23
26
  name: dist
24
27
  path: dist/
25
-
26
28
  publish-testpypi:
27
29
  needs: build
28
30
  runs-on: ubuntu-latest
@@ -38,11 +40,9 @@ jobs:
38
40
  with:
39
41
  name: dist
40
42
  path: dist/
41
-
42
43
  - uses: pypa/gh-action-pypi-publish@release/v1
43
44
  with:
44
45
  repository-url: https://test.pypi.org/legacy/
45
-
46
46
  publish-pypi:
47
47
  needs: publish-testpypi
48
48
  runs-on: ubuntu-latest
@@ -58,5 +58,4 @@ jobs:
58
58
  with:
59
59
  name: dist
60
60
  path: dist/
61
-
62
61
  - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,39 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ release-please:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: write
16
+ pull-requests: write
17
+ actions: write # to dispatch publish.yml below
18
+ steps:
19
+ - uses: googleapis/release-please-action@v4
20
+ id: release
21
+ with:
22
+ config-file: release-please-config.json
23
+ manifest-file: .release-please-manifest.json
24
+
25
+ # release-please-action creates the GitHub Release using the default
26
+ # GITHUB_TOKEN. GitHub doesn't let events produced by that token
27
+ # trigger other workflows (loop-prevention), so publish.yml's
28
+ # `on: release: published` trigger never fires for these releases —
29
+ # dispatch it explicitly instead. workflow_dispatch is exempted from
30
+ # that restriction even when invoked with GITHUB_TOKEN.
31
+ - name: Trigger PyPI publish
32
+ if: ${{ steps.release.outputs.release_created == 'true' }}
33
+ env:
34
+ GH_TOKEN: ${{ github.token }}
35
+ run: |
36
+ gh workflow run publish.yml \
37
+ --repo "${{ github.repository }}" \
38
+ --ref main \
39
+ -f ref="${{ steps.release.outputs.tag_name }}"
@@ -70,3 +70,6 @@ site/
70
70
  # repo deliverable (see commits 8f80a5b, bd693ca on other branches)
71
71
  .backlog-context.md
72
72
  .claude/commands/backlog/
73
+
74
+ # Scratch artifacts — debug dumps, snapshots, never a repo deliverable
75
+ .scratch/
@@ -0,0 +1 @@
1
+ 263
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.6.0"
3
+ }
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0](https://github.com/tstapler/docspan/compare/docspan-v0.5.0...docspan-v0.6.0) (2026-09-18)
9
+
10
+
11
+ ### Features
12
+
13
+ * **gdocs-sectioned-migrate:** wire migrate-sectioned CLI and pull --to-sectioned (Epic 5) ([4f96b1f](https://github.com/tstapler/docspan/commit/4f96b1f58ab3667b21051fdddb00791a2b1a4086))
14
+ * **google-docs:** add blockquote identity fields to paragraph diff model (Epic 1) ([2b31ddf](https://github.com/tstapler/docspan/commit/2b31ddfcd1d3aa62b170818ca8a5dd95f966df95))
15
+ * **google-docs:** cache rendered mermaid PNGs on disk ([#112](https://github.com/tstapler/docspan/issues/112)) ([8c13a03](https://github.com/tstapler/docspan/commit/8c13a039b309f52833294c034f1aebf16d016f74))
16
+ * **google-docs:** pull native blockquote styling back to markdown (Epic 3) ([20aabc9](https://github.com/tstapler/docspan/commit/20aabc9cd64fbef48180219bbf60334f853d0693))
17
+ * **google-docs:** push blockquotes as native indent/borderLeft styling (Epic 2) ([06881a5](https://github.com/tstapler/docspan/commit/06881a51909cc5bab09d5b61cf58046045262070))
18
+ * **google-docs:** warn and add CI signal for legacy blockquote style upgrades (Epic 4) ([984a2e8](https://github.com/tstapler/docspan/commit/984a2e8bc24e48372e321bbf53008079bcc58c5c))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * **cli:** pass full mapping set to cross-doc link resolver on single-file push ([c86dadb](https://github.com/tstapler/docspan/commit/c86dadb532600fd1580e8556a7e0409dac3e44e2))
24
+ * **gdocs-sectioned-migrate:** address verify-review blockers ([0c01490](https://github.com/tstapler/docspan/commit/0c0149075111a56aafb8fd2659582fe2ef6364a0))
25
+ * **google-docs:** comprehensive fix for pull-side image/format lossiness ([#114](https://github.com/tstapler/docspan/issues/114)) ([9dd4834](https://github.com/tstapler/docspan/commit/9dd4834c49cdc5a5a7c427c6a49bce9590898948))
26
+ * **google-docs:** decompose target-side duplicate nodes in replace blocks ([#118](https://github.com/tstapler/docspan/issues/118)) ([e518b14](https://github.com/tstapler/docspan/commit/e518b14d0a820a2a1cb6de25b92deb76d67cd885))
27
+ * **google-docs:** drop unresolvable new images instead of emitting an empty insertInlineImage uri ([#111](https://github.com/tstapler/docspan/issues/111)) ([bc1883d](https://github.com/tstapler/docspan/commit/bc1883dc995b7887f04c08e3d33ab226bf8649cd))
28
+ * **google-docs:** report unreadable bookmark/tab links on tab-scoped pull ([#107](https://github.com/tstapler/docspan/issues/107)) ([be048ca](https://github.com/tstapler/docspan/commit/be048ca00d5e4c40e5d9e617666517d87accb6ae))
29
+ * **google-docs:** tolerate 8-bit RGB quantization in blockquote border detection ([2898434](https://github.com/tstapler/docspan/commit/2898434b5edc2511e552d71ef01ea7393f22ab50))
30
+
31
+ ## [Unreleased]
32
+
33
+ * **google-docs:** removed the `docspan lint`/style-guide warning against `>` blockquotes now that push emits native blockquote styling instead of literal `>`-prefixed text; if a rendering edge case still misrenders a quote post-push, spot it via `push --dry-run`'s structural diff or by visually inspecting the pushed Doc, since no automated check remains for it.
34
+
35
+ ## [0.5.0](https://github.com/tstapler/docspan/compare/docspan-v0.4.0...docspan-v0.5.0) (2026-08-14)
36
+
37
+
38
+ ### Features
39
+
40
+ * **google-docs:** sectioned sync for large document mappings ([#106](https://github.com/tstapler/docspan/issues/106)) ([fe12122](https://github.com/tstapler/docspan/commit/fe121229c8b6b957254020fd2c06241f7506aa80))
41
+
42
+
43
+ ### Bug Fixes
44
+
45
+ * **ci:** dispatch PyPI publish from release-please via workflow_dispatch ([e76ffb8](https://github.com/tstapler/docspan/commit/e76ffb8547bfd7d2d2b453a64a5fb3899445fc5f))
46
+ * **confluence:** report internal anchors instead of writing a link to nowhere ([#105](https://github.com/tstapler/docspan/issues/105)) ([cd38f24](https://github.com/tstapler/docspan/commit/cd38f2415e37814e071a180182454d761cfcbfa9))
47
+ * **google-docs:** reset table cell paragraph style to NORMAL_TEXT on fill ([e6db797](https://github.com/tstapler/docspan/commit/e6db797d51746c738586c3cab171367e23bd5be0))
48
+
8
49
  ## [0.4.0](https://github.com/tstapler/docspan/compare/docspan-v0.3.0...docspan-v0.4.0) (2026-08-13)
9
50
 
10
51
 
@@ -117,6 +158,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
117
158
  - **google-docs:** both pull paths now emit the heading's slug. A default (no `tab_id`) pull
118
159
  goes through Drive's HTML export, which carries the Doc's opaque `#h.abc123` through
119
160
  verbatim; it is upgraded to the slug, so the pulled markdown works as markdown.
161
+ - **google-docs:** a tab-scoped structural pull now renders an ordered-list glyph
162
+ (`DECIMAL`/`ZERO_DECIMAL`/`ALPHA`/`UPPER_ALPHA`/`ROMAN`/`UPPER_ROMAN`) as `1.`/`2.`/…
163
+ instead of a plain `-` bullet, numbered per `(listId, nestingLevel)` in document order.
164
+ One-way: push has no ordered-list concept (every markdown list becomes an unordered
165
+ bullet regardless of source syntax), so a round-trip through push still loses the
166
+ numbering — pre-existing, unconditional on both pull paths, not introduced by this fix.
167
+ - **google-docs:** a Markdown `> ...` blockquote now pushes as a native indented,
168
+ left-bordered paragraph (`indentStart`/`borderLeft`) instead of literal `>` text, and
169
+ pulling it back reconstructs the `> ` prefix from that styling, byte-for-byte round trip
170
+ for plain, nested, list-in-quote, and code-fence-in-quote quotes. A Doc still carrying a
171
+ pre-migration literal-`>` blockquote pulls unchanged and is migrated to the native styling
172
+ the next time its file is pushed for any reason — a one-time rewrite that, like any other
173
+ paragraph rewrite, drops comments anchored to it (see the comments-destroyed limitation
174
+ below).
120
175
 
121
176
  ### Changed
122
177
  - **google-docs:** pass 2 parses and aligns the document once per push instead of three
@@ -128,10 +183,14 @@ Each of these is tracked as a follow-up rather than half-addressed here.
128
183
  - An anchor into a heading in a *different tab* of the same document cannot be resolved and
129
184
  is reported unresolved. The flat `headingId` member resolves against the tab named in the
130
185
  request, so expressing one needs the tabs-aware `Link.heading` member.
131
- - A pull cannot express a `bookmark`/`bookmarkId` link, a link to a tab, or any link inside
132
- a table cell, so those are dropped from the pulled file without a report.
186
+ - A tab-scoped structural pull cannot express a `bookmark`/`bookmarkId` link or a link to a
187
+ tab (including one inside a table cell — cells route through the same link parsing as
188
+ everywhere else). These are absent from the pulled file, but reported: `pull` names each
189
+ unreadable kind in its message rather than dropping them in silence.
133
190
  - Confluence writes an internal anchor as a literal `#fragment` href, which it does not
134
- resolve.
191
+ resolve. `push` now reports this as a warning naming the anchor(s) instead of shipping it
192
+ silently; the href itself is unchanged, since no live instance was available to establish
193
+ what Confluence actually generates for a heading.
135
194
  - An anchor that resolves to nothing is written as plain text, so a later pull replaces the
136
195
  author's `[text](#anchor)` with `text`. The push reports it; nothing does afterwards.
137
196
  - Such a push exits non-zero on every run, with no flag to suppress it.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docspan
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Push and pull markdown to Google Docs and Confluence from a single CLI
5
5
  Project-URL: Homepage, https://github.com/tstapler/docspan
6
6
  Project-URL: Repository, https://github.com/tstapler/docspan
@@ -57,7 +57,7 @@ Description-Content-Type: text/markdown
57
57
 
58
58
  Push and pull markdown to Google Docs and Confluence from a single CLI. docspan provides bidirectional sync with three-way merge conflict detection, structural diff push that preserves comments on unchanged paragraphs, and a simple YAML-based configuration file.
59
59
 
60
- The config file is named `markgate.yaml` — this name is preserved for backward compatibility and will be renamed in v0.2.0.
60
+ The config file is named `markgate.yaml` for backward compatibility. `docspan.yaml` is also recognized — if present in the working directory (and no `markgate.yaml` is there), docspan reads and writes it instead, so a project can rename its config file at its own pace.
61
61
 
62
62
  ---
63
63
 
@@ -76,6 +76,30 @@ The config file is named `markgate.yaml` — this name is preserved for backward
76
76
  pip install docspan
77
77
  ```
78
78
 
79
+ **Optional: mermaid diagram rendering (Google Docs backend only).** Pushing a
80
+ ` ```mermaid ` fence to Google Docs renders it to a PNG via the official
81
+ [mermaid-cli](https://github.com/mermaid-js/mermaid-cli) (`mmdc`), which
82
+ wraps Puppeteer/headless Chrome — there is no pure-Python renderer, so this
83
+ is a Node.js dependency, not a `uv`/`pip` one, and isn't declared in
84
+ `pyproject.toml`. Install it globally so docspan finds a real binary instead
85
+ of falling back to `npx` (which re-fetches on every render):
86
+
87
+ ```bash
88
+ npm install -g @mermaid-js/mermaid-cli # tested against 11.x; older 10.x should also work
89
+ ```
90
+
91
+ If Puppeteer's headless Chrome cache gets corrupted (a truncated download,
92
+ an interrupted npm install), renders fail with an error like `Could not
93
+ find chrome-headless-shell`; re-fetch it with:
94
+
95
+ ```bash
96
+ npx puppeteer browsers install chrome-headless-shell
97
+ ```
98
+
99
+ Without `mmdc` installed and without network access for `npx` to fetch it
100
+ on demand, a mermaid render failure is reported as a push warning, not a
101
+ crash — see [Limitations](docs/backends/google-docs.md#limitations).
102
+
79
103
  ---
80
104
 
81
105
  ## Quick start
@@ -203,6 +227,14 @@ docspan status [--config PATH]
203
227
 
204
228
  Display all configured mappings in a table showing local file, backend, remote ID, and direction.
205
229
 
230
+ ### `docspan style-guide`
231
+
232
+ ```
233
+ docspan style-guide [--backend google_docs|confluence] [--write FILE]
234
+ ```
235
+
236
+ Print backend authoring guidance (e.g. "one image per line on google_docs"). This ships inside the installed package, so re-running it after a `docspan` upgrade picks up new guidance without hand-copying anything. With `--write FILE`, embed it as a marked, idempotent block in a file in your own repo (a `CLAUDE.md`, a style guide doc, etc.) — re-running updates docspan's managed block in place instead of duplicating it.
237
+
206
238
  ### `docspan auth setup`
207
239
 
208
240
  ```
@@ -299,6 +331,9 @@ docspan generates these files in your project directory after first sync:
299
331
  | `.markgate-base/` | Content-addressed store of merge bases |
300
332
  | `{file}.orig` | Backup of local file before merge; deleted after conflict resolution |
301
333
  | `{file}.comments.md` | Comment sidecar (Google Docs + Confluence); written during pull if comments exist |
334
+ | `{file}.mermaid-cache.yaml` | Google Docs: maps each pushed `​```mermaid` fence's rendered-PNG hash back to its source, so a *different* machine pulling the doc can still restore the fence instead of a bare image link. Written during push if the file has any mermaid fences. |
335
+
336
+ **Commit `.markgate-state.json`, `.markgate-base/`, and `{file}.mermaid-cache.yaml` — do not gitignore them.** `.markgate-state.json`/`.markgate-base/` need to be shared for three-way merge to work across machines/teammates; `{file}.mermaid-cache.yaml` is what makes mermaid-fence recovery work across machines at all (Google Docs has no API-writable place to store a diagram's source, only the rendered image — see `mermaid_cache_sidecar.py`'s module docstring for why). `{file}.orig` and `{file}.comments.md` are transient/informational and safe to gitignore if you prefer.
302
337
 
303
338
  ---
304
339
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  Push and pull markdown to Google Docs and Confluence from a single CLI. docspan provides bidirectional sync with three-way merge conflict detection, structural diff push that preserves comments on unchanged paragraphs, and a simple YAML-based configuration file.
7
7
 
8
- The config file is named `markgate.yaml` — this name is preserved for backward compatibility and will be renamed in v0.2.0.
8
+ The config file is named `markgate.yaml` for backward compatibility. `docspan.yaml` is also recognized — if present in the working directory (and no `markgate.yaml` is there), docspan reads and writes it instead, so a project can rename its config file at its own pace.
9
9
 
10
10
  ---
11
11
 
@@ -24,6 +24,30 @@ The config file is named `markgate.yaml` — this name is preserved for backward
24
24
  pip install docspan
25
25
  ```
26
26
 
27
+ **Optional: mermaid diagram rendering (Google Docs backend only).** Pushing a
28
+ ` ```mermaid ` fence to Google Docs renders it to a PNG via the official
29
+ [mermaid-cli](https://github.com/mermaid-js/mermaid-cli) (`mmdc`), which
30
+ wraps Puppeteer/headless Chrome — there is no pure-Python renderer, so this
31
+ is a Node.js dependency, not a `uv`/`pip` one, and isn't declared in
32
+ `pyproject.toml`. Install it globally so docspan finds a real binary instead
33
+ of falling back to `npx` (which re-fetches on every render):
34
+
35
+ ```bash
36
+ npm install -g @mermaid-js/mermaid-cli # tested against 11.x; older 10.x should also work
37
+ ```
38
+
39
+ If Puppeteer's headless Chrome cache gets corrupted (a truncated download,
40
+ an interrupted npm install), renders fail with an error like `Could not
41
+ find chrome-headless-shell`; re-fetch it with:
42
+
43
+ ```bash
44
+ npx puppeteer browsers install chrome-headless-shell
45
+ ```
46
+
47
+ Without `mmdc` installed and without network access for `npx` to fetch it
48
+ on demand, a mermaid render failure is reported as a push warning, not a
49
+ crash — see [Limitations](docs/backends/google-docs.md#limitations).
50
+
27
51
  ---
28
52
 
29
53
  ## Quick start
@@ -151,6 +175,14 @@ docspan status [--config PATH]
151
175
 
152
176
  Display all configured mappings in a table showing local file, backend, remote ID, and direction.
153
177
 
178
+ ### `docspan style-guide`
179
+
180
+ ```
181
+ docspan style-guide [--backend google_docs|confluence] [--write FILE]
182
+ ```
183
+
184
+ Print backend authoring guidance (e.g. "one image per line on google_docs"). This ships inside the installed package, so re-running it after a `docspan` upgrade picks up new guidance without hand-copying anything. With `--write FILE`, embed it as a marked, idempotent block in a file in your own repo (a `CLAUDE.md`, a style guide doc, etc.) — re-running updates docspan's managed block in place instead of duplicating it.
185
+
154
186
  ### `docspan auth setup`
155
187
 
156
188
  ```
@@ -247,6 +279,9 @@ docspan generates these files in your project directory after first sync:
247
279
  | `.markgate-base/` | Content-addressed store of merge bases |
248
280
  | `{file}.orig` | Backup of local file before merge; deleted after conflict resolution |
249
281
  | `{file}.comments.md` | Comment sidecar (Google Docs + Confluence); written during pull if comments exist |
282
+ | `{file}.mermaid-cache.yaml` | Google Docs: maps each pushed `​```mermaid` fence's rendered-PNG hash back to its source, so a *different* machine pulling the doc can still restore the fence instead of a bare image link. Written during push if the file has any mermaid fences. |
283
+
284
+ **Commit `.markgate-state.json`, `.markgate-base/`, and `{file}.mermaid-cache.yaml` — do not gitignore them.** `.markgate-state.json`/`.markgate-base/` need to be shared for three-way merge to work across machines/teammates; `{file}.mermaid-cache.yaml` is what makes mermaid-fence recovery work across machines at all (Google Docs has no API-writable place to store a diagram's source, only the rendered image — see `mermaid_cache_sidecar.py`'s module docstring for why). `{file}.orig` and `{file}.comments.md` are transient/informational and safe to gitignore if you prefer.
250
285
 
251
286
  ---
252
287
 
@@ -56,7 +56,7 @@ mappings:
56
56
  !!! warning
57
57
  - **Comments destroyed on push for edited paragraphs**: The structural diff preserves comments on unchanged paragraphs, but any paragraph that is deleted and reinserted loses its comments. This is a known v0.1.0 limitation.
58
58
  - **Images push and pull**: `![alt](./local.png)` uploads the local file to Drive and references it by URI; an `https://` URL is referenced directly, bypassing upload. Only a standalone image on its own line is supported — one mixed into a paragraph alongside running text is left as plain text. Missing files, files over 50MB, and unsupported formats (SVG) are reported as push warnings rather than blocking the write or crashing.
59
- - **Mermaid diagrams push as rendered PNGs**: a fenced ` ```mermaid ` block is rendered to a raster PNG (via `mermaid-cli`/`mmdc`, shelled out to — install with `npm install -g @mermaid-js/mermaid-cli`, or it's fetched on demand through `npx`) and pushed as an inline image, since `insertInlineImage` has no native mermaid or SVG support. A render failure (missing Node.js/mermaid-cli, invalid diagram syntax, timeout) is reported as a push warning, not a crash. There is no pull-side reconstruction — a mermaid diagram round-trips back to markdown as a plain image reference, not a ` ```mermaid ` fence.
59
+ - **Mermaid diagrams push as rendered PNGs**: a fenced ` ```mermaid ` block is rendered to a raster PNG (via the official `mermaid-cli`/`mmdc`, shelled out to — install with `npm install -g @mermaid-js/mermaid-cli`, tested against 11.x, or it's fetched on demand through `npx`, which re-downloads on every render) and pushed as an inline image, since `insertInlineImage` has no native mermaid or SVG support. See [Install](../../README.md#install) for setup, including the fix for a corrupted Puppeteer headless-Chrome cache. A render failure (missing Node.js/mermaid-cli, invalid diagram syntax, timeout) is reported as a push warning, not a crash. Successful renders are cached on disk (keyed on diagram text, render scale, and the resolved `mmdc --version`) under `$XDG_CACHE_HOME/docspan/mermaid`, so an unchanged fence across repeat pushes skips the mermaid-cli/Puppeteer invocation entirely. There is no pull-side reconstruction — a mermaid diagram round-trips back to markdown as a plain image reference, not a ` ```mermaid ` fence.
60
60
  - **Pulled image URIs can go stale**: a pulled `![alt](src)` link is Google's `contentUri` for that embedded object, which Google's API docs say may change over time even when the image itself is unchanged. The push structural diff keys image identity on `alt`/width/height, not this URI, so a rotated `contentUri` alone will not cause the paragraph to be deleted and reinserted (which would destroy any comment anchored to it) — but the stale URI persisted in your markdown file can still surface as a one-sided edit in `docspan conflicts resolve`'s three-way diff.
61
61
  - **Table cells hold one paragraph**: a markdown table cell is pushed as a single
62
62
  paragraph, and inline formatting inside it (bold, monospace, links, internal
@@ -65,3 +65,6 @@ mappings:
65
65
  table created by the current push gets its cell styling on the *next* push —
66
66
  docspan reports both rather than failing silently.
67
67
  - **Rate limiting**: The Google Docs API allows 300 requests per minute per project. Large documents with many changed paragraphs may trigger rate limit errors.
68
+ - **Blockquotes render as an indented, left-bordered callout**: a Markdown `> ...` line is pushed as a paragraph with native `indentStart`/`borderLeft` styling (no literal `>` text), and pulling that paragraph back reconstructs the `> ` prefix from that styling, byte-for-byte, independent of the visual border. A Doc that still has a *pre-migration* blockquote (pushed by an older docspan version as literal `>` text) keeps rendering as plain text until the file containing it is pushed again for any reason — at that point every legacy blockquote in the file is deleted and reinserted with native styling in that same push, which, per the comments limitation above, destroys any comment anchored to one of those paragraphs. This is a one-time cost per file, not a recurring one. Very large files with many legacy blockquotes could in principle hit the Google Docs API's `batchUpdate` payload-size cap during that one migrating push; this has not been reproduced or quantified against a real document.
69
+
70
+ Both `docspan push --dry-run` and a real `docspan push` print a `STYLE_UPGRADE_COUNT=<N>` line for each run, counting the legacy blockquote paragraphs about to be (or that were) rewritten to native styling — `0` when there are none. This is a plain, machine-parsable line meant for CI: grep it out of the output rather than parsing the human-readable `⚠` warnings above it. Pass `--fail-on-comment-loss` to make `push` exit non-zero when `STYLE_UPGRADE_COUNT` is greater than zero; without the flag the count is reporting-only and never affects the exit code or blocks the write.
@@ -16,12 +16,14 @@ pip install docspan
16
16
  uv add docspan
17
17
  ```
18
18
 
19
- ## Install via pipx
19
+ ## Install via uv tool / uvx
20
20
 
21
21
  ```bash
22
- pipx install docspan
22
+ uv tool install docspan
23
23
  ```
24
24
 
25
+ To run it without installing, use `uvx docspan`.
26
+
25
27
  ---
26
28
 
27
29
  ## Google Docs Auth Setup
@@ -0,0 +1,38 @@
1
+ # ADR-001: Blockquotes render as native indent + `borderLeft`, using a docspan-owned marker, migrated lazily
2
+
3
+ ## Status
4
+ Accepted
5
+
6
+ ## Context
7
+
8
+ `markdown_to_paragraph_parser.py`'s `_walk_block_quote` currently renders a markdown `>` blockquote by prefixing each produced paragraph's text with literal `"> "` characters (`_prefix_node_text`). Google Docs has no built-in blockquote paragraph style, so the literal `> ` text is the only signal a reader sees — it renders as plain, unstyled text starting with a greater-than sign, which users report as looking broken rather than as an intentional callout (`requirements.md`'s Problem Statement).
9
+
10
+ Three approaches were considered (`implementation/plan.md`'s Step 0.5 creative pass):
11
+
12
+ 1. **Tighten lint/style-guide wording only** — tells authors not to use `>`, doesn't fix the rendering.
13
+ 2. **Strip `>` and italicize** — simple, but loses the "set apart as an aside" visual signal the job-to-be-done research (`research/ux.md` §5) confirms is the actual reason authors reach for `>`.
14
+ 3. **Native `indentStart` + `borderLeft` styling, chosen here** — matches the convention independently observed across Pandoc→docx (`Block Text` style, indent-led), Notion (indent + left border, no fill), and Confluence Cloud's newer blockquote block (`research/ux.md` §1), and is corroborated as the de facto community detection heuristic by `gd2md-html` (`research/build-vs-buy.md` §4).
15
+
16
+ Google's Docs API v1 exposes `ParagraphStyle.indentStart` (a `Dimension`) and `ParagraphStyle.borderLeft` (a `ParagraphBorder`: color, width, dashStyle, padding) as the primitives. There is no first-class "blockquote" style; docspan must define and own a marker combination on both fields to safely round-trip identity through them.
17
+
18
+ ## Decision
19
+
20
+ - A blockquote paragraph is represented by two new `DocsParagraphNode` fields, `is_blockquote: bool` and `quote_depth: int`, decoupled entirely from `node.text` (no more literal `"> "` embedded in the text).
21
+ - On push, `is_blockquote`/`quote_depth` translate to `indentStart` (scaled by `quote_depth` × a fixed points-per-level constant) and a full `borderLeft` `ParagraphBorder` object using a distinctive, docspan-owned color/width/dashStyle combination (`BLOCKQUOTE_BORDER_MARKER`, values fixed by a live-Doc spike, Epic 0 in `implementation/plan.md`) — computed by one shared helper (`_blockquote_paragraph_style_fields`) called from both the insert-path and restyle-path `updateParagraphStyle` request sites in `docs_request_builder.py`, so the two paths cannot drift.
22
+ - On pull, `docs_structure_parser.py`'s `_parse_paragraph` recognizes that exact marker/indent combination and sets `is_blockquote`/`quote_depth` accordingly; `nodes_to_markdown.py` reconstructs `"> " * quote_depth` markdown prefixes at render time via a new `_group_blockquote_runs` grouping stage (composed as the outer stage around the existing `_group_code_runs`) and a new `BlockquoteNodeRenderer`.
23
+ - `is_blockquote`/`quote_depth` participate in `_node_key` (diff-alignment identity) but are excluded from `_content_key` (restyle-vs-rewrite classification), mirroring the existing `render_prefix`/image-`src` precedent — so a pure blockquote-styling change can still be expressed as an in-place restyle rather than a destructive delete+reinsert wherever possible.
24
+ - **Migration is lazy and unconditional — no feature flag.** A previously-pushed literal-`> `-text paragraph is left exactly as-is until the next time that specific paragraph is pushed with a change; at that point it is deleted and reinserted (since removing the embedded `"> "` from `node.text` is itself a text change), and any comment anchored to it is lost — the same cost as any other delete+reinsert today. This is a deliberate, accepted, one-time-per-paragraph cost, not an oversight; `push --dry-run` calls it out via a new `style_upgrade` reason on `HighRiskParagraph` (`implementation/plan.md` Epic 4) so it's distinguishable from unrelated diff-engine churn.
25
+ - **List-in-quote indent stacking is additive by construction, not by explicit combined-indent code.** `docs_request_builder.py`'s existing list handling derives bullet indentation from `CreateParagraphBulletsRequest`, keyed on leading-tab count in the paragraph's text — not from any `paragraphStyle.indentStart` docspan sets. The blockquote's `indentStart` (from `quote_depth`) is a wholly separate paragraph-style field. Because the two indent sources are independent fields consumed by independent Docs mechanisms, a paragraph that is both a list item and a blockquote gets both indents simultaneously with no interaction code required — confirmed against a live Doc in Epic 0's spike (`implementation/plan.md` Story 2.6), not assumed from reading the code alone.
26
+ - `borderLeft` was independently confirmed (not merely assumed) to render unconditionally per paragraph — Google Docs' visual border-coalescing behavior applies to `borderTop`/`borderBottom`/`borderBetween` sub-fields, not `borderLeft` — so no live-Doc spike is required to settle the coalescing question specifically; it is resolved by this ADR, not deferred.
27
+ - `lint.py`'s `find_blockquote_issues` and the corresponding `GOOGLE_DOCS_STYLE_GUIDE` bullet warning against `>` are **deleted outright**, not narrowed. The lint rule's own module docstring already scopes it specifically to google_docs (Confluence already renders blockquotes natively); once google_docs also does, there is no backend left for the rule to protect against, and a narrowed "for future backends" version would be speculative, untested YAGNI.
28
+ - `is_blockquote`/`quote_depth` are an intentionally-paired invariant, not two independent fields: `is_blockquote == (quote_depth > 0)` is enforced in `DocsParagraphNode.__post_init__` (raising `ValueError` otherwise), rather than collapsed into a single derived field. A single field was considered and rejected here specifically because it would ripple a field-shape change through every story in `implementation/plan.md` that constructs both fields together; a construction-time invariant closes the same illegal-state gap at a fraction of the cost.
29
+ - Marker detection on pull compares only the `color`/`width`/`dashStyle` sub-fields docspan itself writes into `borderLeft`, not whole-dict equality against `BLOCKQUOTE_BORDER_MARKER`. This is deliberate: Google's Docs API may echo back additional normalized sub-fields (e.g. a default `padding` or `unit`) that were never sent, and blanket `==` would then falsely report "not a blockquote" for docspan's own paragraphs.
30
+ - `BLOCKQUOTE_BORDER_MARKER`/`BLOCKQUOTE_INDENT_PT_PER_LEVEL` are owned by `docs_structure_parser.py` (the pull-side module) and imported — never redefined or copied — by `docs_request_builder.py` (the push-side module), so there is exactly one source of truth for the marker's identity, consistent with `_blockquote_paragraph_style_fields` avoiding the same class of drift within the push side alone.
31
+
32
+ ## Consequences
33
+
34
+ - **Comment-loss risk is accepted, not eliminated**, for every already-pushed blockquote in every mapped document, on its first post-migration push. This is the direct tradeoff for fixing rendering without a migration tool or feature flag; `implementation/plan.md`'s Migration Plan and Epic 4 exist specifically to make this visible in `--dry-run`, not to prevent it.
35
+ - **False-positive marker detection remains probabilistic, not exact.** A human-applied Docs UI border that happens to coincidentally match `BLOCKQUOTE_BORDER_MARKER`'s exact color/width/dashStyle would be misdetected as a docspan blockquote on pull. This is accepted as an explicit non-goal of perfect detection (mitigated only by choosing a visually distinctive marker in Epic 0), consistent with the same class of magic-constant risk the codebase already accepts elsewhere (`render_prefix`), and is documented here so a future bug report isn't read as a design failure.
36
+ - **Marker migration risk**: if `BLOCKQUOTE_BORDER_MARKER`'s value is ever changed in a future release, documents pushed under the old marker silently stop being recognized as blockquotes on pull. Unlike the render-glyph fix (`31b4edd`, which uses a Unicode category rather than a hardcoded codepoint specifically to avoid this class of problem), there is no equivalent "any value of this shape" fallback available for an arbitrary border/indent combination, since arbitrary colors/widths are also legitimate for non-docspan content. If the marker must ever change, a follow-up migration path will be needed; none is designed here. Forward-looking note for future maintainers: if the Docs API ever exposes a first-class custom-paragraph-style-id (as opposed to raw style-field values), that would let a future release stop relying on a probabilistic border/indent match entirely — worth checking for before inventing a bespoke migration scheme.
37
+ - **Documented non-goal**: this feature delivers indent+border "this is set apart" signaling only. It explicitly does not implement GitHub-style `[!NOTE]`/`[!WARNING]` admonition syntax, background tint, or icons (the heavier "callout" affordance Notion/Confluence separately offer) — confirmed by `research/ux.md` §5 as a materially bigger, distinct feature, not a corner cut in this one. A future feature request for colored callout boxes is new scope, not evidence this project under-delivered.
38
+ - Two empirical unknowns are *not* resolved by this ADR and remain open, gated on a live-Doc spike (`implementation/plan.md` Epic 0, Unresolved Questions 1-3): the exact marker color/width/dashStyle values, whether omitting a `ParagraphBorder` sub-field on write leaves it unset vs. resets it to a Docs-side default, and whether a table cell inherits an adjacent blockquote's `indentStart`/`borderLeft` the way it's confirmed to inherit `namedStyleType` (`e6db797` precedent).