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.
- {docspan-0.4.0 → docspan-0.6.0}/.github/workflows/publish.yml +6 -7
- docspan-0.6.0/.github/workflows/release-please.yml +39 -0
- {docspan-0.4.0 → docspan-0.6.0}/.gitignore +3 -0
- docspan-0.6.0/.mypy-error-baseline +1 -0
- docspan-0.6.0/.release-please-manifest.json +3 -0
- {docspan-0.4.0 → docspan-0.6.0}/CHANGELOG.md +62 -3
- {docspan-0.4.0 → docspan-0.6.0}/PKG-INFO +37 -2
- {docspan-0.4.0 → docspan-0.6.0}/README.md +36 -1
- {docspan-0.4.0 → docspan-0.6.0}/docs/backends/google-docs.md +4 -1
- {docspan-0.4.0 → docspan-0.6.0}/docs/install.md +4 -2
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/decisions/ADR-001-native-blockquote-indent-and-border-styling.md +38 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/design/ux.md +353 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/adversarial-review.md +13 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/architecture-review.md +30 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/epic-0-spike-findings.md +144 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/plan.md +373 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/pre-mortem.md +17 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/implementation/validation.md +88 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/requirements.md +91 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/architecture.md +207 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/build-vs-buy.md +36 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/features.md +76 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/pitfalls.md +204 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/stack.md +65 -0
- docspan-0.6.0/project_plans/gdocs-native-blockquotes/research/ux.md +160 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/design/ux.md +297 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/adversarial-review.md +30 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/architecture-review.md +40 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/plan.md +564 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/pre-mortem.md +16 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/implementation/validation.md +79 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/requirements.md +70 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/architecture.md +244 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/build-vs-buy.md +133 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/features.md +155 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/pitfalls.md +271 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/stack.md +190 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-migrate/research/ux.md +147 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/decisions/ADR-001-manifest-yaml-sidecar-keyed-by-heading-id.md +24 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/decisions/ADR-002-reorder-as-in-place-move.md +25 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/decisions/ADR-003-sectioned-pull-always-structural-path.md +19 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/adversarial-review.md +28 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/architecture-review.md +31 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/plan.md +296 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/pre-mortem.md +16 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/implementation/validation.md +62 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/requirements.md +80 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/architecture.md +216 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/build-vs-buy.md +64 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/features.md +257 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/pitfalls.md +278 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/stack.md +54 -0
- docspan-0.6.0/project_plans/gdocs-sectioned-sync/research/ux.md +146 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/base.py +30 -0
- docspan-0.6.0/src/docspan/backends/confluence/anchors.py +111 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/backend.py +28 -3
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/auth.py +24 -14
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/backend.py +870 -74
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/comments.py +83 -2
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/docs_request_builder.py +475 -18
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/docs_structure_parser.py +291 -7
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/image_source.py +54 -6
- docspan-0.6.0/src/docspan/backends/google_docs/manifest.py +193 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/markdown_to_paragraph_parser.py +57 -52
- docspan-0.6.0/src/docspan/backends/google_docs/mermaid_appendix.py +174 -0
- docspan-0.6.0/src/docspan/backends/google_docs/mermaid_cache_sidecar.py +99 -0
- docspan-0.6.0/src/docspan/backends/google_docs/mermaid_renderer.py +256 -0
- docspan-0.6.0/src/docspan/backends/google_docs/migration.py +989 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/nodes_to_markdown.py +145 -6
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/onboarding.py +4 -3
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/projection.py +12 -1
- docspan-0.6.0/src/docspan/backends/google_docs/pulled_image_recovery.py +239 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/push_preview.py +85 -13
- docspan-0.6.0/src/docspan/backends/google_docs/section_splitter.py +194 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/cli/main.py +368 -12
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/config.py +103 -7
- docspan-0.6.0/src/docspan/core/atomic_dir.py +82 -0
- docspan-0.6.0/src/docspan/core/orchestrator.py +877 -0
- docspan-0.6.0/src/docspan/core/paths.py +38 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/xdg.py +10 -0
- docspan-0.6.0/src/docspan/style_guide.py +81 -0
- docspan-0.6.0/tests/fixtures/blockquote_border_marker_spike.json +52 -0
- docspan-0.6.0/tests/test_atomic_dir.py +108 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_cli.py +216 -4
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_code_block_granularity.py +81 -22
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_config.py +137 -0
- docspan-0.6.0/tests/test_confluence_anchors.py +175 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_confluence_backend.py +35 -0
- docspan-0.6.0/tests/test_confluence_push_dead_anchors.py +90 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_docs_request_builder.py +203 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_docs_structure_parser.py +176 -2
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_gdocs_images.py +28 -1
- docspan-0.6.0/tests/test_gdocs_mermaid.py +309 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_gdocs_push_pipeline.py +243 -1
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_gdocs_tables_and_styles.py +28 -3
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_comments.py +107 -1
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_docs_backend.py +1947 -37
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_heading_anchors.py +79 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_heading_identity.py +60 -0
- docspan-0.6.0/tests/test_lint.py +16 -0
- docspan-0.6.0/tests/test_manifest.py +115 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_markdown_to_paragraph_parser.py +90 -7
- docspan-0.6.0/tests/test_mermaid_appendix.py +95 -0
- docspan-0.6.0/tests/test_mermaid_cache_sidecar.py +115 -0
- docspan-0.6.0/tests/test_migrate_sectioned_cli.py +590 -0
- docspan-0.6.0/tests/test_migration.py +827 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_nodes_to_markdown.py +17 -5
- docspan-0.6.0/tests/test_orchestrator.py +948 -0
- docspan-0.6.0/tests/test_paths_data_uri_guard.py +51 -0
- docspan-0.6.0/tests/test_pulled_image_recovery.py +300 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_push_preview.py +135 -22
- docspan-0.6.0/tests/test_section_splitter.py +185 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_state.py +27 -0
- docspan-0.6.0/tests/test_style_guide.py +13 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_tabs.py +123 -0
- docspan-0.4.0/.github/workflows/release-please.yml +0 -18
- docspan-0.4.0/.mypy-error-baseline +0 -1
- docspan-0.4.0/.release-please-manifest.json +0 -3
- docspan-0.4.0/src/docspan/backends/google_docs/mermaid_renderer.py +0 -100
- docspan-0.4.0/src/docspan/core/orchestrator.py +0 -360
- docspan-0.4.0/src/docspan/core/paths.py +0 -8
- docspan-0.4.0/tests/test_gdocs_mermaid.py +0 -119
- docspan-0.4.0/tests/test_orchestrator.py +0 -336
- {docspan-0.4.0 → docspan-0.6.0}/.github/workflows/ci.yml +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/CONTRIBUTING.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/Procfile +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/RAILWAY_SETUP.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/doc.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/docs/backends/confluence.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/docs/commands.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/docs/configuration.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/docs/contributing.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/docs/index.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/docspan.yaml.example +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/markgate.yaml.example +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/mkdocs.yml +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/modules/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/modules/auth.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/modules/conflict_handler.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/modules/converter.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/modules/gdrive_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/modules/sync_engine.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/bidirectional-comments/plan.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/adversarial-review.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/plan.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/release-checklist.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/implementation/validation.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/requirements.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/architecture.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/features.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/google-docs-push.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/pitfalls.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/docspan-release/research/stack.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/gdocs-tables-inline-styles/plan.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/decisions/ADR-001-merge3-dependency.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/decisions/ADR-002-base-content-sidecar-store.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/implementation/adversarial-review.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/implementation/plan.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/implementation/validation.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/requirements.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/architecture.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/features.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/pitfalls.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/markgate-sync/research/stack.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/decisions/ADR-001-checklist-state-as-literal-text.md +0 -0
- {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
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/decisions/ADR-003-no-comment-anchor-migration.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/feature-gap-report.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/adversarial-review.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/architecture-review.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/plan.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/pre-mortem.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/implementation/validation.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/requirements.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/architecture.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/build-vs-buy.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/features.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/pitfalls.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/stack.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/research/ux.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/project_plans/wedding-planning-workflow/workflow-runbook.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/pyproject.toml +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/release-please-config.json +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/requirements.txt +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/runtime.txt +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/__main__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/comparator.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/converter.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/converters.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/interfaces.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/nodes.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/parser.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/validators.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/adf/visitors.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/loader.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/models.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/config/validation.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/ast.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/frontmatter.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/mermaid.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/extensions/wikilinks.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/inline_parser.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/markdown/parser.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/markdown_file.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/page.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/path_utils.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/results.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/models/sync_status.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/attachment_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/base_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/comment_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/crawler.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/label_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/page_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/space_client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/confluence/services/confluence/url_parser.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/checkbox_state.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/client.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/converter.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/cross_doc_links.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/heading_anchors.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/registry.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/backends/google_docs/tabs.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/cli/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/merge.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/src/docspan/core/state.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/sync.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/README.md +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/main.tf +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/outputs.tf +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/terraform/gcp/variables.tf +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/terraform/main.tf +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/terraform/variables.tf +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/__init__.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/conftest.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/fixtures/github_slugger_vectors.json +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_checkbox_state.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_conflict_resolution.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_confluence_mermaid_push_pipeline.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_content_key_pooling_performance.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_converter.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_cross_doc_link_issues.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_cross_doc_links.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_cross_doc_links_backend.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_oauth.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_google_onboarding.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_merge.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_registry.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_restyle_destruction_rate.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_span_trailing_newline.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_table_cell_spans.py +0 -0
- {docspan-0.4.0 → docspan-0.6.0}/tests/test_xdg_central_config.py +0 -0
- {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 }}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
263
|
|
@@ -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
|
|
132
|
-
a table cell
|
|
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.
|
|
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`
|
|
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`
|
|
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**: `` 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
|
|
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 `` 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
|
|
19
|
+
## Install via uv tool / uvx
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
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).
|