textflowkit 0.1.9__tar.gz → 0.1.10__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 (200) hide show
  1. {textflowkit-0.1.9 → textflowkit-0.1.10}/.github/workflows/ci.yml +1 -1
  2. {textflowkit-0.1.9 → textflowkit-0.1.10}/CHANGELOG.md +55 -0
  3. {textflowkit-0.1.9 → textflowkit-0.1.10}/PKG-INFO +97 -20
  4. {textflowkit-0.1.9 → textflowkit-0.1.10}/README.md +96 -19
  5. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/adapters.md +76 -4
  6. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/architecture.md +46 -3
  7. textflowkit-0.1.10/docs/assets/architecture-overview.svg +213 -0
  8. textflowkit-0.1.10/docs/assets/local-ui-flow.svg +155 -0
  9. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/index.html +41 -9
  10. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/install.md +34 -1
  11. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/release-checklist.md +24 -1
  12. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/roadmap.md +24 -0
  13. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/site-deployment.md +10 -2
  14. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/user-manual.md +108 -16
  15. {textflowkit-0.1.9 → textflowkit-0.1.10}/pyproject.toml +10 -1
  16. textflowkit-0.1.10/scripts/smoke_installed_wheel.py +436 -0
  17. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/__init__.py +1 -1
  18. textflowkit-0.1.10/src/textflowkit/ui/__init__.py +13 -0
  19. textflowkit-0.1.10/src/textflowkit/ui/app.py +579 -0
  20. textflowkit-0.1.10/src/textflowkit/ui/capabilities.py +195 -0
  21. textflowkit-0.1.10/src/textflowkit/ui/launcher.py +686 -0
  22. textflowkit-0.1.10/src/textflowkit/ui/ownership.py +602 -0
  23. textflowkit-0.1.10/src/textflowkit/ui/paths.py +179 -0
  24. textflowkit-0.1.10/src/textflowkit/ui/security.py +200 -0
  25. textflowkit-0.1.10/src/textflowkit/ui/static/app.css +285 -0
  26. textflowkit-0.1.10/src/textflowkit/ui/static/app.js +756 -0
  27. textflowkit-0.1.10/src/textflowkit/ui/static/index.html +226 -0
  28. textflowkit-0.1.10/src/textflowkit/ui/uploads.py +247 -0
  29. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_landing_commands.py +28 -11
  30. textflowkit-0.1.10/tests/test_ui_assets_and_capabilities.py +294 -0
  31. textflowkit-0.1.10/tests/test_ui_consoleless_logging.py +88 -0
  32. textflowkit-0.1.10/tests/test_ui_existing_routes_unchanged.py +86 -0
  33. textflowkit-0.1.10/tests/test_ui_launcher.py +224 -0
  34. textflowkit-0.1.10/tests/test_ui_ownership_and_shutdown.py +477 -0
  35. textflowkit-0.1.10/tests/test_ui_packaging.py +153 -0
  36. textflowkit-0.1.10/tests/test_ui_real_serve.py +88 -0
  37. textflowkit-0.1.10/tests/test_ui_security.py +168 -0
  38. textflowkit-0.1.10/tests/test_ui_submission_and_download.py +260 -0
  39. textflowkit-0.1.10/tests/test_ui_uploads.py +201 -0
  40. textflowkit-0.1.9/docs/assets/architecture-overview.svg +0 -189
  41. textflowkit-0.1.9/scripts/smoke_installed_wheel.py +0 -219
  42. {textflowkit-0.1.9 → textflowkit-0.1.10}/.dockerignore +0 -0
  43. {textflowkit-0.1.9 → textflowkit-0.1.10}/.env.example +0 -0
  44. {textflowkit-0.1.9 → textflowkit-0.1.10}/.gitattributes +0 -0
  45. {textflowkit-0.1.9 → textflowkit-0.1.10}/.github/dependabot.yml +0 -0
  46. {textflowkit-0.1.9 → textflowkit-0.1.10}/.github/workflows/publish-pypi.yml +0 -0
  47. {textflowkit-0.1.9 → textflowkit-0.1.10}/.gitignore +0 -0
  48. {textflowkit-0.1.9 → textflowkit-0.1.10}/.pre-commit-config.yaml +0 -0
  49. {textflowkit-0.1.9 → textflowkit-0.1.10}/CONTRIBUTING.md +0 -0
  50. {textflowkit-0.1.9 → textflowkit-0.1.10}/Dockerfile +0 -0
  51. {textflowkit-0.1.9 → textflowkit-0.1.10}/LEGAL.md +0 -0
  52. {textflowkit-0.1.9 → textflowkit-0.1.10}/LICENSE +0 -0
  53. {textflowkit-0.1.9 → textflowkit-0.1.10}/SECURITY.md +0 -0
  54. {textflowkit-0.1.9 → textflowkit-0.1.10}/compose.yaml +0 -0
  55. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/.nojekyll +0 -0
  56. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/404.html +0 -0
  57. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/assets/whistle-resume.svg +0 -0
  58. {textflowkit-0.1.9 → textflowkit-0.1.10}/docs/sources.md +0 -0
  59. {textflowkit-0.1.9 → textflowkit-0.1.10}/scripts/fetch_published_fonts.py +0 -0
  60. {textflowkit-0.1.9 → textflowkit-0.1.10}/scripts/smoke_live_youtube.py +0 -0
  61. {textflowkit-0.1.9 → textflowkit-0.1.10}/scripts/verify_readme_release.py +0 -0
  62. {textflowkit-0.1.9 → textflowkit-0.1.10}/scripts/verify_release_ci.py +0 -0
  63. {textflowkit-0.1.9 → textflowkit-0.1.10}/scripts/verify_release_versions.py +0 -0
  64. {textflowkit-0.1.9 → textflowkit-0.1.10}/scripts/write_release_manifest.py +0 -0
  65. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/adapters/__init__.py +0 -0
  66. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/adapters/http_server.py +0 -0
  67. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/adapters/mcp_server.py +0 -0
  68. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/assets/README.md +0 -0
  69. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/assets/selftest-speech.wav +0 -0
  70. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/cli.py +0 -0
  71. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/__init__.py +0 -0
  72. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/batch.py +0 -0
  73. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/bind.py +0 -0
  74. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/cancel.py +0 -0
  75. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/checkpoint.py +0 -0
  76. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/diarize.py +0 -0
  77. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/engine.py +0 -0
  78. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/executor.py +0 -0
  79. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/jobs.py +0 -0
  80. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/model.py +0 -0
  81. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/paths.py +0 -0
  82. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/pipeline.py +0 -0
  83. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/retrieval.py +0 -0
  84. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/runner.py +0 -0
  85. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/service.py +0 -0
  86. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/sqlite_store.py +0 -0
  87. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/startup.py +0 -0
  88. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/submission.py +0 -0
  89. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/timeutil.py +0 -0
  90. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/translate.py +0 -0
  91. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/whistle.py +0 -0
  92. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/core/whistle_assets.py +0 -0
  93. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/__init__.py +0 -0
  94. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/_cue_layout.py +0 -0
  95. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/_cue_text.py +0 -0
  96. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/_rename_excl.py +0 -0
  97. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/_rename_noreplace.py +0 -0
  98. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/docx.py +0 -0
  99. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/markdown.py +0 -0
  100. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/pdf.py +0 -0
  101. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/srt.py +0 -0
  102. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/txt.py +0 -0
  103. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/render/vtt.py +0 -0
  104. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/sources/__init__.py +0 -0
  105. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/sources/acquire.py +0 -0
  106. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/sources/detect.py +0 -0
  107. {textflowkit-0.1.9 → textflowkit-0.1.10}/src/textflowkit/sources/scratch.py +0 -0
  108. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/__init__.py +0 -0
  109. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/ollama_stub.py +0 -0
  110. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_acquire.py +0 -0
  111. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_adapters.py +0 -0
  112. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_admission_ownership.py +0 -0
  113. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_atomic_resume.py +0 -0
  114. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_atomic_resume_aba.py +0 -0
  115. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_batch.py +0 -0
  116. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_batch_item_boundary.py +0 -0
  117. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_batch_resume_shared.py +0 -0
  118. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_bind.py +0 -0
  119. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cancellation_finalization.py +0 -0
  120. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_checkpoint.py +0 -0
  121. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_checkpoint_single_copy.py +0 -0
  122. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cli.py +0 -0
  123. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cli_batch_feedback.py +0 -0
  124. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cli_owning_recovery.py +0 -0
  125. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cli_resume_batch.py +0 -0
  126. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cli_stage_feedback.py +0 -0
  127. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cli_stdout_format.py +0 -0
  128. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_confined_decoder_boundary.py +0 -0
  129. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_confined_indirect_media.py +0 -0
  130. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_confined_resume_boundary.py +0 -0
  131. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_container_example.py +0 -0
  132. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_cue_layout.py +0 -0
  133. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_diarize.py +0 -0
  134. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_diarize_example_flags.py +0 -0
  135. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_diarize_token_docs.py +0 -0
  136. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_docx_publication_identity.py +0 -0
  137. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_done_output_integrity.py +0 -0
  138. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_empty_formats_resume.py +0 -0
  139. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_engine_adapters.py +0 -0
  140. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_engine_cache.py +0 -0
  141. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_engine_faster.py +0 -0
  142. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_engine_quiet.py +0 -0
  143. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_executor.py +0 -0
  144. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_executor_store_failure.py +0 -0
  145. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_export.py +0 -0
  146. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_export_file_mode.py +0 -0
  147. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_export_preflight.py +0 -0
  148. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_faster_whisper_docs.py +0 -0
  149. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_fetch_published_fonts.py +0 -0
  150. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_font_notices.py +0 -0
  151. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_http_host_boundary.py +0 -0
  152. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_http_trusted_proxy.py +0 -0
  153. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_job_progress.py +0 -0
  154. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_jobs.py +0 -0
  155. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_landing_page.py +0 -0
  156. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_legal_summary.py +0 -0
  157. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_linux_no_hardlink.py +0 -0
  158. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_live_smoke_script.py +0 -0
  159. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_macos_no_hardlink.py +0 -0
  160. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_mcp_batch_cookie_docs.py +0 -0
  161. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_mcp_http_boundary.py +0 -0
  162. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_mcp_protocol_cleanliness.py +0 -0
  163. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_mcp_terminal_guidance.py +0 -0
  164. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_mcp_tool_descriptions.py +0 -0
  165. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_mcp_work_root.py +0 -0
  166. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_model.py +0 -0
  167. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_partial_output_resume.py +0 -0
  168. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_partial_pdf_resume.py +0 -0
  169. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_paths.py +0 -0
  170. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_pipeline_integrity.py +0 -0
  171. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_pipeline_resume_setup.py +0 -0
  172. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_production_browser_cookies.py +0 -0
  173. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_publish_ci_gate.py +0 -0
  174. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_publish_fonts_wiring.py +0 -0
  175. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_publish_manifest_wiring.py +0 -0
  176. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_publish_workflow_gate.py +0 -0
  177. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_readme_layout.py +0 -0
  178. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_readme_release_guard.py +0 -0
  179. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_release_boundaries.py +0 -0
  180. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_release_manifest.py +0 -0
  181. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_release_surfaces.py +0 -0
  182. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_render.py +0 -0
  183. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_retrieval.py +0 -0
  184. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_sdist_allowlist.py +0 -0
  185. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_service_profile.py +0 -0
  186. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_site_not_found.py +0 -0
  187. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_sources.py +0 -0
  188. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_stage_reuse_resume.py +0 -0
  189. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_startup_recovery.py +0 -0
  190. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_stdio_protocol.py +0 -0
  191. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_submission.py +0 -0
  192. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_submission_validation.py +0 -0
  193. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_translate.py +0 -0
  194. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_translate_completeness.py +0 -0
  195. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_verify_release_versions.py +0 -0
  196. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_whistle_assets.py +0 -0
  197. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_whistle_defaults.py +0 -0
  198. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_whistle_engine.py +0 -0
  199. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_whistle_job_resume.py +0 -0
  200. {textflowkit-0.1.9 → textflowkit-0.1.10}/tests/test_windows_no_hardlink.py +0 -0
@@ -90,6 +90,6 @@ jobs:
90
90
  python -m pip install "setuptools>=83" "build>=1.2"
91
91
  python -m build --wheel
92
92
  python -m build --wheel --outdir dist packages/textflowkit-fonts
93
- - name: Smoke all three installed entry points
93
+ - name: Smoke all four installed entry points (CLI, MCP, HTTP, served UI)
94
94
  shell: bash
95
95
  run: python scripts/smoke_installed_wheel.py dist/textflowkit-*.whl dist/textflowkit_fonts-*.whl
@@ -1,5 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.1.10 — 2026-10-06
4
+
5
+ **The local browser interface ships.** The loopback-only browser workspace
6
+ (`textflowkit-ui`) is now part of the core package rather than source-only, so a
7
+ regular install of this release carries the command. Core version: 0.1.10;
8
+ unchanged optional fonts: 0.1.6.
9
+
10
+ ### Added
11
+
12
+ - **Local browser interface in the core package.** `textflowkit-ui` serves a
13
+ point-and-click workspace on **this machine only**: it binds `127.0.0.1` (never
14
+ off the machine, whatever `TEXTFLOWKIT_ALLOW_REMOTE` is set to) and opens a
15
+ browser once serving. It mounts the existing developer HTTP app under `/api`
16
+ **unchanged** — the same routes, submission contract, and job store — and adds
17
+ only transport: a loopback session layer, a streamed upload, a fixed-format
18
+ download, in-workspace playback, and a capability endpoint. It adds **no new
19
+ dependency** beyond the existing `http` extra (FastAPI + uvicorn).
20
+ - **Durable jobs, cancel and resume in the workspace.** Jobs and checkpoints are
21
+ stored durably under the per-user data directory unless
22
+ `TEXTFLOWKIT_DB` / `TEXTFLOWKIT_WORK_ROOT` / `TEXTFLOWKIT_OUTPUT_ROOT` is set.
23
+ A dropped file is streamed and capped at **2 GiB** by default
24
+ (`TEXTFLOWKIT_UI_MAX_UPLOAD_BYTES` overrides it). A running job can be
25
+ cancelled, and a durable job resumed.
26
+ - **Windows desktop shortcut.** `textflowkit-ui --create-shortcut` creates a Start
27
+ Menu entry that launches with `pythonw` (no console) and needs no `pywin32`.
28
+ Under `pythonw` a startup failure is logged under the per-user data directory
29
+ and shown in a message box rather than failing silently.
30
+ - **Controlled shutdown that drains.** **Stop server** asks this process's own
31
+ uvicorn server to exit through its supported `should_exit` flag; the normal
32
+ lifespan teardown **waits for current jobs to finish** before draining the
33
+ worker pool rather than interrupting them, so a running job's saved progress
34
+ survives and is resumable. It never sends a signal to a pid.
35
+
36
+ ### Changed
37
+
38
+ - **Documentation surfaces.** README, user manual, install notes, developer
39
+ manual, architecture guide, roadmap, release checklist, site deployment, the
40
+ landing page, and the checked-in SVG diagrams now describe the local browser
41
+ interface as a shipped feature and a fifth door, and remove the earlier
42
+ "unreleased candidate / install from source only" wording.
43
+ - **No endpoint-breaking change.** The CLI, Python API, MCP server, and JSON HTTP
44
+ adapter are unchanged in this release; the optional engines, diarization, and
45
+ translation keep their documented dependency requirements.
46
+
47
+ ### Verification boundary
48
+
49
+ Automated coverage runs the UI's routes, packaging, ownership/shutdown, and
50
+ security boundaries. A **native Windows** machine, in a real browser, proved the
51
+ workspace end to end (drop a file, submit, download, stop). That is one platform's
52
+ browser proof, **not** a live Linux or macOS UI run — those platforms are covered
53
+ by automated tests only, and cross-platform CI (Windows/Linux/macOS) is a separate
54
+ step run by the coordinator. **No live optional-backend (translation or
55
+ diarization) run through the UI is claimed.** The engine and packaging changes of
56
+ v0.1.9 are unchanged by this release.
57
+
3
58
  ## v0.1.9 — 2026-10-05
4
59
 
5
60
  **Whistle is now the default engine.** This release ships the Whistle integration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: textflowkit
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Cross-platform media transcription toolkit: URL or file in, timestamped transcripts and subtitle files out.
5
5
  Project-URL: Homepage, https://www.textflowkit.org/
6
6
  Project-URL: Issues, https://github.com/scottconverse/textflowkit/issues
@@ -67,7 +67,7 @@ Cross-platform media transcription toolkit. **One core, one CLI, thin adapters.*
67
67
  [User manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md) ·
68
68
  [Developer and integration manual](https://github.com/scottconverse/textflowkit/blob/main/docs/adapters.md)
69
69
 
70
- **Current release: [v0.1.9](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.9).**
70
+ **Current release: [v0.1.10](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.10).**
71
71
 
72
72
  The [static site deployment](https://github.com/scottconverse/textflowkit/blob/main/docs/site-deployment.md) is hosted on Cloudflare
73
73
  Pages. GitHub remains the source and CI host; the website does not run the
@@ -128,10 +128,11 @@ DOCX and PDF are available through the optional `export` extra.
128
128
 
129
129
  ## Architecture
130
130
 
131
- ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/architecture-overview.svg)
131
+ ![TextFlowKit shared-core architecture: five thin entry points feed one core. The CLI, Python API, MCP, HTTP API, and the local browser UI (a fifth door that mounts the existing HTTP app under /api and shares the same job store, adding transport only, not a new pipeline) sit above one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.10/docs/assets/architecture-overview.svg)
132
132
 
133
- The design principle is **one engine, three doors**. Everything of substance lives in
134
- the core; the interfaces are thin.
133
+ The design principle is **one shared core, thin doors**. Everything of substance
134
+ lives in the core; the interfaces are thin — the local browser UI is a fifth door
135
+ that adds only transport over the existing HTTP app and shared job store.
135
136
 
136
137
  | Layer | Path | Responsibility |
137
138
  |---|---|---|
@@ -141,6 +142,7 @@ the core; the interfaces are thin.
141
142
  | **CLI** | `src/textflowkit/cli.py` | Reference interface (subprocess-friendly) |
142
143
  | **MCP** | `src/textflowkit/adapters/mcp_server.py` | stdio + Streamable HTTP, for AI harnesses |
143
144
  | **HTTP** | `src/textflowkit/adapters/http_server.py` | JSON API, for software products and web frontends |
145
+ | **Local UI** | `src/textflowkit/ui` | Loopback-only browser workspace over the same HTTP app and job store |
144
146
 
145
147
  Because the core owns the pipeline, adding a door is cheap — and adding a platform
146
148
  means writing one source adapter, not another tool.
@@ -294,7 +296,7 @@ textflowkit export ./transcript.json --format vtt
294
296
  needs no PyTorch, so a fresh `pip install textflowkit` stays small and pulls no
295
297
  torch; `openai-whisper` is an explicitly selectable opt-in engine.
296
298
 
297
- ![Whistle bounded block run and durable resume: a decoded PCM WAV is split into 26-second cores with up to 2 seconds of context (each clip ≤ 30 s), each core runs as one owned child with forced telemetry-off flags, a partial checkpoint is written after each core, and a resume validates the source and decoded-WAV hashes then re-runs only the unfinished cores.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/whistle-resume.svg)
299
+ ![Whistle bounded block run and durable resume: a decoded PCM WAV is split into 26-second cores with up to 2 seconds of context (each clip ≤ 30 s), each core runs as one owned child with forced telemetry-off flags, a partial checkpoint is written after each core, and a resume validates the source and decoded-WAV hashes then re-runs only the unfinished cores.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.10/docs/assets/whistle-resume.svg)
298
300
 
299
301
  - **CPU only, no torch.** It runs a pinned native binary and downloads one pinned
300
302
  model on first use. Naming a GPU device is refused rather than silently
@@ -397,6 +399,66 @@ contract checks cover it, and running an actual build is deliberately out of
397
399
  scope here, so no image is claimed to build or start. See
398
400
  [the container example](https://github.com/scottconverse/textflowkit/blob/main/docs/adapters.md#container-example-dockerfile-and-compose).
399
401
 
402
+ ## Local browser interface
403
+
404
+ For point-and-click use there is a local browser workspace that runs on **this
405
+ machine only**, reuses the same pipeline and job store, and adds no dependency of
406
+ its own beyond the `http` extra. It ships with the core package as of v0.1.10:
407
+ a regular install of the current release carries it.
408
+
409
+ ```bash
410
+ python -m pip install 'textflowkit[http,export]==0.1.10'
411
+ textflowkit-ui
412
+ ```
413
+
414
+ On Windows you can create a desktop shortcut when you want one:
415
+
416
+ ```powershell
417
+ textflowkit-ui --create-shortcut # Start Menu entry (pythonw, no console)
418
+ textflowkit-ui --create-shortcut --shortcut-dir 'C:\path\to\folder'
419
+ ```
420
+
421
+ The shortcut is created only on request and needs no `pywin32`. Under `pythonw`
422
+ there is no console, so a startup failure is written to a log file under the
423
+ per-user data directory and shown in a message box rather than failing silently.
424
+ The normal operator path is to launch from that shortcut; the console command
425
+ above is equivalent.
426
+
427
+ **This is a local app, not a public website.** The [landing page](https://www.textflowkit.org/)
428
+ is static documentation served by Cloudflare Pages; it does **not** run the
429
+ transcription engine and offers no upload form. The browser workspace exists only
430
+ on the machine that installed it.
431
+
432
+ - **Loopback only.** It binds `127.0.0.1` (never off the machine, whatever
433
+ `TEXTFLOWKIT_ALLOW_REMOTE` is set to) and opens a browser once it is serving.
434
+ - **One owner per database.** A second launch against the same database opens the
435
+ UI already running rather than starting a competing server. **Stop server** in
436
+ the workspace shuts the local server down without a terminal, waiting for the
437
+ current jobs to finish rather than interrupting them. Shutdown **drains** — it
438
+ lets in-flight jobs complete; it does **not** auto-cancel them. Cancel a job
439
+ first to stop it early.
440
+ - **Durable jobs with cancel and resume.** Jobs and checkpoints are stored
441
+ durably under the per-user data directory unless you set `TEXTFLOWKIT_DB` /
442
+ `TEXTFLOWKIT_WORK_ROOT` / `TEXTFLOWKIT_OUTPUT_ROOT`. A dropped file is capped
443
+ at **2 GiB** by default (`TEXTFLOWKIT_UI_MAX_UPLOAD_BYTES` overrides it).
444
+ In-workspace playback uses the browser's native player on the **currently
445
+ selected** media, so it depends on what that browser can decode; it is a
446
+ convenience preview, not a format guarantee.
447
+ - **No UI analytics.** The page loads no CDN asset and no analytics, and the
448
+ session token lives only in the page — never in a URL, cookie, or log line.
449
+ - **What uses the network.** The page itself loads nothing remote. A **URL
450
+ source** is downloaded from the internet; an engine's **model** may be
451
+ downloaded on first use; and **remote or cloud translation** (an advanced
452
+ option) sends transcript text to that backend. With local media and a local
453
+ translation model, no transcript leaves the machine.
454
+ - `--port`, `--no-browser`, and `--version` are available.
455
+
456
+ The other doors are unchanged: the CLI, Python API, MCP, and HTTP adapters keep
457
+ their existing behavior, and the optional engines, diarization, and translation
458
+ retain their documented dependency requirements. See the
459
+ [user manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md#7-local-browser-interface)
460
+ and [architecture guide](https://github.com/scottconverse/textflowkit/blob/main/docs/architecture.md#one-owner-per-database).
461
+
400
462
  ## Durable, bounded, cancellable
401
463
 
402
464
  ```bash
@@ -411,23 +473,38 @@ boundary. See [docs/adapters.md](https://github.com/scottconverse/textflowkit/bl
411
473
 
412
474
  The MCP and HTTP adapters are **job-based**: submission returns a job id
413
475
  immediately and clients poll for completion. The CLI uses the same core but
414
- waits for the result. This lets AI harnesses, software products, and a future
415
- web frontend share the job contract without blocking a request.
476
+ waits for the result. This lets AI harnesses, software products, and the local
477
+ browser interface share the job contract without blocking a request.
416
478
 
417
479
  ## Status
418
480
 
419
- **v0.1.9 release.** Core, CLI, MCP, and HTTP have automated
420
- coverage. This release makes **Whistle the default engine** — a native CPU-only
421
- transcriber that needs no PyTorch — and moves `openai-whisper` to the optional
422
- `whisper` extra, selected explicitly with `--engine whisper` or
481
+ **v0.1.10 release.** Core, CLI, MCP, HTTP, and the local browser interface have
482
+ automated coverage. This release **ships the local browser interface**
483
+ (`textflowkit-ui`) in the core package: a loopback-only workspace that reuses the
484
+ same pipeline, submission contract, and job store, with a durable store, cancel
485
+ and resume, and a Windows desktop shortcut (`textflowkit-ui --create-shortcut`).
486
+ It adds **no new dependency** beyond the existing `http` extra, so a regular
487
+ `pip install 'textflowkit[http,export]==0.1.10'` carries it. The engine, CLI,
488
+ Python, MCP, and HTTP surfaces are unchanged and there is **no endpoint-breaking
489
+ change**. It builds on v0.1.9, which made **Whistle the default engine** — a
490
+ native CPU-only transcriber that needs no PyTorch — and moved `openai-whisper` to
491
+ the optional `whisper` extra, selected explicitly with `--engine whisper` or
423
492
  `engine="whisper"`. Legacy engine aliases are preserved, so older saved jobs and
424
493
  `transcribe()` calls still decode. Whistle refuses an unsupported platform,
425
- language, or GPU request instead of silently falling back. Its engine's
494
+ language, or GPU request instead of silently falling back; its engine's
426
495
  verification boundary is in [Whistle (default engine)](#whistle-default-engine)
427
- above. Release publication uses the tag workflow, which requires successful
428
- exact-commit main CI before PyPI uploads and creates the public GitHub release
429
- only afterward. Check the linked release for artifacts and workflow status; local
430
- source verification is not a fresh PyPI-install or individual-harness receipt.
496
+ above.
497
+
498
+ **Verification limit.** The local browser interface is covered by an automated
499
+ suite, and the UI's behavior was proved on a **native Windows** machine in a real
500
+ browser. No live optional-backend (translation/diarization) run through the UI is
501
+ claimed, and **no live Linux or macOS UI run was performed here** — those
502
+ platforms are covered by automated tests only, and CI across Windows, Linux, and
503
+ macOS is the coordinator's separate step. Release publication uses the tag
504
+ workflow, which requires successful exact-commit main CI before PyPI uploads and
505
+ creates the public GitHub release only afterward. Check the linked release for
506
+ artifacts and workflow status; local source verification is not a fresh
507
+ PyPI-install or individual-harness receipt.
431
508
 
432
509
  The v0.1.8 release carried four fixes for the 2026-10-01 post-release audit-lite
433
510
  (findings AL-001 – AL-004): CLI owning-process startup recovery so an interrupted
@@ -439,8 +516,8 @@ that precede it. The v0.1.7 source candidate was verified with real CLI, HTTP an
439
516
  MCP speech, seven-format exports and completed resume; the v0.1.8 runtime fixes
440
517
  were independently checked through fresh-process CLI restart probes, real SQLite
441
518
  setup-failure tests, and the full Windows test suite. Those checks are not new
442
- individual-harness receipts. That is the previous release's record, not evidence
443
- for v0.1.9.
519
+ individual-harness receipts. That is the previous releases' record, not evidence
520
+ for v0.1.10.
444
521
 
445
522
  The v0.1.6 release was the post-v0.1.5 review repair set: security hardening
446
523
  for media acquisition and the HTTP and MCP adapters, safer subtitle wrapping and
@@ -459,7 +536,7 @@ and both [core](https://pypi.org/project/textflowkit/0.1.6/) and
459
536
  [fonts](https://pypi.org/project/textflowkit-fonts/0.1.6/) PyPI projects are
460
537
  live — that is the v0.1.6 release's evidence, not a receipt for a later release.
461
538
  Everything from here to the end of this section is the v0.1.6 release's
462
- historical record as published: it is not re-verified for v0.1.9, whose separate release evidence is not supplied by these historical paragraphs.
539
+ historical record as published: it is not re-verified for v0.1.10, whose separate release evidence is not supplied by these historical paragraphs.
463
540
  Merged-main CI passed 16/16 on the tagged commit; the published wheel and
464
541
  sdist digests match the GitHub release assets and their SHA-256 list; and a fresh
465
542
  Windows Python 3.12 install of `textflowkit[export,mcp,http]==0.1.6` from PyPI
@@ -8,7 +8,7 @@ Cross-platform media transcription toolkit. **One core, one CLI, thin adapters.*
8
8
  [User manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md) ·
9
9
  [Developer and integration manual](https://github.com/scottconverse/textflowkit/blob/main/docs/adapters.md)
10
10
 
11
- **Current release: [v0.1.9](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.9).**
11
+ **Current release: [v0.1.10](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.10).**
12
12
 
13
13
  The [static site deployment](https://github.com/scottconverse/textflowkit/blob/main/docs/site-deployment.md) is hosted on Cloudflare
14
14
  Pages. GitHub remains the source and CI host; the website does not run the
@@ -69,10 +69,11 @@ DOCX and PDF are available through the optional `export` extra.
69
69
 
70
70
  ## Architecture
71
71
 
72
- ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/architecture-overview.svg)
72
+ ![TextFlowKit shared-core architecture: five thin entry points feed one core. The CLI, Python API, MCP, HTTP API, and the local browser UI (a fifth door that mounts the existing HTTP app under /api and shares the same job store, adding transport only, not a new pipeline) sit above one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.10/docs/assets/architecture-overview.svg)
73
73
 
74
- The design principle is **one engine, three doors**. Everything of substance lives in
75
- the core; the interfaces are thin.
74
+ The design principle is **one shared core, thin doors**. Everything of substance
75
+ lives in the core; the interfaces are thin — the local browser UI is a fifth door
76
+ that adds only transport over the existing HTTP app and shared job store.
76
77
 
77
78
  | Layer | Path | Responsibility |
78
79
  |---|---|---|
@@ -82,6 +83,7 @@ the core; the interfaces are thin.
82
83
  | **CLI** | `src/textflowkit/cli.py` | Reference interface (subprocess-friendly) |
83
84
  | **MCP** | `src/textflowkit/adapters/mcp_server.py` | stdio + Streamable HTTP, for AI harnesses |
84
85
  | **HTTP** | `src/textflowkit/adapters/http_server.py` | JSON API, for software products and web frontends |
86
+ | **Local UI** | `src/textflowkit/ui` | Loopback-only browser workspace over the same HTTP app and job store |
85
87
 
86
88
  Because the core owns the pipeline, adding a door is cheap — and adding a platform
87
89
  means writing one source adapter, not another tool.
@@ -235,7 +237,7 @@ textflowkit export ./transcript.json --format vtt
235
237
  needs no PyTorch, so a fresh `pip install textflowkit` stays small and pulls no
236
238
  torch; `openai-whisper` is an explicitly selectable opt-in engine.
237
239
 
238
- ![Whistle bounded block run and durable resume: a decoded PCM WAV is split into 26-second cores with up to 2 seconds of context (each clip ≤ 30 s), each core runs as one owned child with forced telemetry-off flags, a partial checkpoint is written after each core, and a resume validates the source and decoded-WAV hashes then re-runs only the unfinished cores.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/whistle-resume.svg)
240
+ ![Whistle bounded block run and durable resume: a decoded PCM WAV is split into 26-second cores with up to 2 seconds of context (each clip ≤ 30 s), each core runs as one owned child with forced telemetry-off flags, a partial checkpoint is written after each core, and a resume validates the source and decoded-WAV hashes then re-runs only the unfinished cores.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.10/docs/assets/whistle-resume.svg)
239
241
 
240
242
  - **CPU only, no torch.** It runs a pinned native binary and downloads one pinned
241
243
  model on first use. Naming a GPU device is refused rather than silently
@@ -338,6 +340,66 @@ contract checks cover it, and running an actual build is deliberately out of
338
340
  scope here, so no image is claimed to build or start. See
339
341
  [the container example](https://github.com/scottconverse/textflowkit/blob/main/docs/adapters.md#container-example-dockerfile-and-compose).
340
342
 
343
+ ## Local browser interface
344
+
345
+ For point-and-click use there is a local browser workspace that runs on **this
346
+ machine only**, reuses the same pipeline and job store, and adds no dependency of
347
+ its own beyond the `http` extra. It ships with the core package as of v0.1.10:
348
+ a regular install of the current release carries it.
349
+
350
+ ```bash
351
+ python -m pip install 'textflowkit[http,export]==0.1.10'
352
+ textflowkit-ui
353
+ ```
354
+
355
+ On Windows you can create a desktop shortcut when you want one:
356
+
357
+ ```powershell
358
+ textflowkit-ui --create-shortcut # Start Menu entry (pythonw, no console)
359
+ textflowkit-ui --create-shortcut --shortcut-dir 'C:\path\to\folder'
360
+ ```
361
+
362
+ The shortcut is created only on request and needs no `pywin32`. Under `pythonw`
363
+ there is no console, so a startup failure is written to a log file under the
364
+ per-user data directory and shown in a message box rather than failing silently.
365
+ The normal operator path is to launch from that shortcut; the console command
366
+ above is equivalent.
367
+
368
+ **This is a local app, not a public website.** The [landing page](https://www.textflowkit.org/)
369
+ is static documentation served by Cloudflare Pages; it does **not** run the
370
+ transcription engine and offers no upload form. The browser workspace exists only
371
+ on the machine that installed it.
372
+
373
+ - **Loopback only.** It binds `127.0.0.1` (never off the machine, whatever
374
+ `TEXTFLOWKIT_ALLOW_REMOTE` is set to) and opens a browser once it is serving.
375
+ - **One owner per database.** A second launch against the same database opens the
376
+ UI already running rather than starting a competing server. **Stop server** in
377
+ the workspace shuts the local server down without a terminal, waiting for the
378
+ current jobs to finish rather than interrupting them. Shutdown **drains** — it
379
+ lets in-flight jobs complete; it does **not** auto-cancel them. Cancel a job
380
+ first to stop it early.
381
+ - **Durable jobs with cancel and resume.** Jobs and checkpoints are stored
382
+ durably under the per-user data directory unless you set `TEXTFLOWKIT_DB` /
383
+ `TEXTFLOWKIT_WORK_ROOT` / `TEXTFLOWKIT_OUTPUT_ROOT`. A dropped file is capped
384
+ at **2 GiB** by default (`TEXTFLOWKIT_UI_MAX_UPLOAD_BYTES` overrides it).
385
+ In-workspace playback uses the browser's native player on the **currently
386
+ selected** media, so it depends on what that browser can decode; it is a
387
+ convenience preview, not a format guarantee.
388
+ - **No UI analytics.** The page loads no CDN asset and no analytics, and the
389
+ session token lives only in the page — never in a URL, cookie, or log line.
390
+ - **What uses the network.** The page itself loads nothing remote. A **URL
391
+ source** is downloaded from the internet; an engine's **model** may be
392
+ downloaded on first use; and **remote or cloud translation** (an advanced
393
+ option) sends transcript text to that backend. With local media and a local
394
+ translation model, no transcript leaves the machine.
395
+ - `--port`, `--no-browser`, and `--version` are available.
396
+
397
+ The other doors are unchanged: the CLI, Python API, MCP, and HTTP adapters keep
398
+ their existing behavior, and the optional engines, diarization, and translation
399
+ retain their documented dependency requirements. See the
400
+ [user manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md#7-local-browser-interface)
401
+ and [architecture guide](https://github.com/scottconverse/textflowkit/blob/main/docs/architecture.md#one-owner-per-database).
402
+
341
403
  ## Durable, bounded, cancellable
342
404
 
343
405
  ```bash
@@ -352,23 +414,38 @@ boundary. See [docs/adapters.md](https://github.com/scottconverse/textflowkit/bl
352
414
 
353
415
  The MCP and HTTP adapters are **job-based**: submission returns a job id
354
416
  immediately and clients poll for completion. The CLI uses the same core but
355
- waits for the result. This lets AI harnesses, software products, and a future
356
- web frontend share the job contract without blocking a request.
417
+ waits for the result. This lets AI harnesses, software products, and the local
418
+ browser interface share the job contract without blocking a request.
357
419
 
358
420
  ## Status
359
421
 
360
- **v0.1.9 release.** Core, CLI, MCP, and HTTP have automated
361
- coverage. This release makes **Whistle the default engine** — a native CPU-only
362
- transcriber that needs no PyTorch — and moves `openai-whisper` to the optional
363
- `whisper` extra, selected explicitly with `--engine whisper` or
422
+ **v0.1.10 release.** Core, CLI, MCP, HTTP, and the local browser interface have
423
+ automated coverage. This release **ships the local browser interface**
424
+ (`textflowkit-ui`) in the core package: a loopback-only workspace that reuses the
425
+ same pipeline, submission contract, and job store, with a durable store, cancel
426
+ and resume, and a Windows desktop shortcut (`textflowkit-ui --create-shortcut`).
427
+ It adds **no new dependency** beyond the existing `http` extra, so a regular
428
+ `pip install 'textflowkit[http,export]==0.1.10'` carries it. The engine, CLI,
429
+ Python, MCP, and HTTP surfaces are unchanged and there is **no endpoint-breaking
430
+ change**. It builds on v0.1.9, which made **Whistle the default engine** — a
431
+ native CPU-only transcriber that needs no PyTorch — and moved `openai-whisper` to
432
+ the optional `whisper` extra, selected explicitly with `--engine whisper` or
364
433
  `engine="whisper"`. Legacy engine aliases are preserved, so older saved jobs and
365
434
  `transcribe()` calls still decode. Whistle refuses an unsupported platform,
366
- language, or GPU request instead of silently falling back. Its engine's
435
+ language, or GPU request instead of silently falling back; its engine's
367
436
  verification boundary is in [Whistle (default engine)](#whistle-default-engine)
368
- above. Release publication uses the tag workflow, which requires successful
369
- exact-commit main CI before PyPI uploads and creates the public GitHub release
370
- only afterward. Check the linked release for artifacts and workflow status; local
371
- source verification is not a fresh PyPI-install or individual-harness receipt.
437
+ above.
438
+
439
+ **Verification limit.** The local browser interface is covered by an automated
440
+ suite, and the UI's behavior was proved on a **native Windows** machine in a real
441
+ browser. No live optional-backend (translation/diarization) run through the UI is
442
+ claimed, and **no live Linux or macOS UI run was performed here** — those
443
+ platforms are covered by automated tests only, and CI across Windows, Linux, and
444
+ macOS is the coordinator's separate step. Release publication uses the tag
445
+ workflow, which requires successful exact-commit main CI before PyPI uploads and
446
+ creates the public GitHub release only afterward. Check the linked release for
447
+ artifacts and workflow status; local source verification is not a fresh
448
+ PyPI-install or individual-harness receipt.
372
449
 
373
450
  The v0.1.8 release carried four fixes for the 2026-10-01 post-release audit-lite
374
451
  (findings AL-001 – AL-004): CLI owning-process startup recovery so an interrupted
@@ -380,8 +457,8 @@ that precede it. The v0.1.7 source candidate was verified with real CLI, HTTP an
380
457
  MCP speech, seven-format exports and completed resume; the v0.1.8 runtime fixes
381
458
  were independently checked through fresh-process CLI restart probes, real SQLite
382
459
  setup-failure tests, and the full Windows test suite. Those checks are not new
383
- individual-harness receipts. That is the previous release's record, not evidence
384
- for v0.1.9.
460
+ individual-harness receipts. That is the previous releases' record, not evidence
461
+ for v0.1.10.
385
462
 
386
463
  The v0.1.6 release was the post-v0.1.5 review repair set: security hardening
387
464
  for media acquisition and the HTTP and MCP adapters, safer subtitle wrapping and
@@ -400,7 +477,7 @@ and both [core](https://pypi.org/project/textflowkit/0.1.6/) and
400
477
  [fonts](https://pypi.org/project/textflowkit-fonts/0.1.6/) PyPI projects are
401
478
  live — that is the v0.1.6 release's evidence, not a receipt for a later release.
402
479
  Everything from here to the end of this section is the v0.1.6 release's
403
- historical record as published: it is not re-verified for v0.1.9, whose separate release evidence is not supplied by these historical paragraphs.
480
+ historical record as published: it is not re-verified for v0.1.10, whose separate release evidence is not supplied by these historical paragraphs.
404
481
  Merged-main CI passed 16/16 on the tagged commit; the published wheel and
405
482
  sdist digests match the GitHub release assets and their SHA-256 list; and a fresh
406
483
  Windows Python 3.12 install of `textflowkit[export,mcp,http]==0.1.6` from PyPI
@@ -1,15 +1,16 @@
1
1
  # Developer and integration manual
2
2
 
3
- Current release: [v0.1.9](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.9).
3
+ Current release: [v0.1.10](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.10).
4
4
  Start with the [user manual](user-manual.md) for everyday use. Harness versions
5
5
  in the compatibility table below identify **historical test sessions**, not
6
6
  the version of TextFlowKit currently published.
7
7
 
8
8
  textflowkit has one core and several thin doors. Nothing is duplicated between
9
- them: the CLI, the MCP server, and the HTTP API all call
10
- `textflowkit.core.submission` and share one job model.
9
+ them: the CLI, the MCP server, the HTTP API, and the local browser
10
+ interface (`textflowkit-ui`) all call `textflowkit.core.submission` and share one
11
+ job model.
11
12
 
12
- ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](assets/architecture-overview.svg)
13
+ ![TextFlowKit shared-core architecture: five thin entry points feed one core. The CLI, Python API, MCP, HTTP API, and the local browser UI (a fifth door that mounts the existing HTTP app under /api and shares the same job store, adding transport only, not a new pipeline) sit above one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](assets/architecture-overview.svg)
13
14
 
14
15
  ```
15
16
  ┌──────────────────────┐
@@ -505,6 +506,77 @@ agree. So if you do leave it on, put it behind a proxy that appends its own
505
506
  observation rather than forwarding the caller's header, and keep
506
507
  `FORWARDED_ALLOW_IPS` narrow.
507
508
 
509
+ ## Local browser interface (`textflowkit-ui`)
510
+
511
+ `textflowkit-ui` is a fourth thin door beside the CLI, MCP, and HTTP entry
512
+ points. It mounts the developer HTTP app (`adapters.http_server`) under `/api`
513
+ **unchanged** - the same routes, the same submission contract, the same job
514
+ store - and adds only transport: a loopback-only browser session layer, a
515
+ streamed upload, a fixed-format download, and a capability endpoint. It
516
+ **ships in the core package as of v0.1.10** (it needs the `http` extra, which
517
+ supplies FastAPI and uvicorn, and adds no new dependency). It has no public site
518
+ of its own: it is a local app, not a hosted service.
519
+
520
+ ```bash
521
+ python -m pip install 'textflowkit[http,export]==0.1.10'
522
+ textflowkit-ui # serve on 127.0.0.1 and open a browser
523
+ textflowkit-ui --no-browser --port 8901
524
+ ```
525
+
526
+ ![TextFlowKit local browser interface request flow: the operator opens the workspace from a desktop shortcut or the textflowkit-ui command, which launches a loopback-only server on 127.0.0.1 that holds an exclusive owner lock on the durable SQLite database; the operator drops a file or pastes a URL, a file is streamed to a per-user upload folder capped at 2 GiB by default; both become a submission through the same shared submission contract the CLI, MCP, and HTTP doors use, writing a durable job that runs through the shared core (Whistle by default or an explicit optional engine) decoding with ffmpeg and transcribing on this machine; a finished transcript is searchable, copyable, downloadable in a fixed format list, and playable with the browser's native player on the currently selected media.](assets/local-ui-flow.svg)
527
+
528
+ Because it is a browser door and not a product, its rules are **stricter** than
529
+ the developer HTTP app's:
530
+
531
+ - **Loopback only, always.** It binds `127.0.0.1` and refuses any non-loopback
532
+ host, and it does **not** honour `TEXTFLOWKIT_ALLOW_REMOTE` - that opt-in widens
533
+ the developer API, and an unauthenticated browser interface must not be
534
+ reachable off the machine.
535
+ - **A session capability on every mutator.** The shell embeds a per-process
536
+ random token in a `<meta>` element; every POST carries it in
537
+ `x-textflowkit-ui-capability`. A request must also pass a loopback host, a
538
+ provably-loopback peer, and an `Origin` that is *exactly* this UI's own origin.
539
+ The token never appears in a URL, a cookie, or a log line.
540
+ - **No external asset, no telemetry.** The shell and its CSS/JS are served from
541
+ the package; the page loads no CDN script and no analytics. (`/ui/capabilities`
542
+ reports what the install can do without loading a model or downloading
543
+ anything.) Network *is* used for a URL source, an engine's first-use model
544
+ download, and a remote/cloud translation backend - see the translation caveat
545
+ [above](#translation): a `:cloud` model or a remote `TEXTFLOWKIT_OLLAMA_HOST`
546
+ sends transcript text to that host. Local media and a local translation model
547
+ keep transcripts on the machine.
548
+
549
+ ### One owner per database
550
+
551
+ The UI runs startup recovery and the worker pool, so a store is owned by one UI
552
+ process. It takes an **operating-system file lock** (`msvcrt` on Windows, `flock`
553
+ on POSIX) on a persistent sibling of the database, keyed by the *resolved* path,
554
+ *before* recovery runs. The kernel holds the lock for the live holder and releases
555
+ it on exit **or crash**, so a killed run never blocks the next launch. A second
556
+ launch discovers the running instance's loopback URL from a small record beside
557
+ the lock and opens that instead of starting a competing server. Ownership is
558
+ proved by the held lock plus a matching record - never by signalling or inspecting
559
+ a pid. The same rule as every door still applies: **do not point two processes at
560
+ one database file.** See the [architecture guide](architecture.md#one-owner-per-database).
561
+
562
+ ### Uploads and downloads
563
+
564
+ A dropped file is streamed as the **raw request body** to a fresh, server-named
565
+ file under the per-user work directory (default cap 2 GiB,
566
+ `TEXTFLOWKIT_UI_MAX_UPLOAD_BYTES` overrides it); the size is capped both by the
567
+ declared length and while streaming, and a refused, aborted, or oversized upload
568
+ leaves no partial file. The staged path is generated by the server, never taken
569
+ from the request, and a `mode: "upload"` submission names only an `upload_id`
570
+ that is resolved back inside the uploads directory. Downloads come from a **fixed
571
+ format allowlist** (TXT, Markdown, SRT, VTT, JSON, DOCX, PDF) rendered through the
572
+ same `render` layer the other doors use; the filename is built from the job id,
573
+ never from caller input.
574
+
575
+ The UI adds **no new storage variable**: it fills in `TEXTFLOWKIT_DB`,
576
+ `TEXTFLOWKIT_WORK_ROOT`, and `TEXTFLOWKIT_OUTPUT_ROOT` with per-user defaults
577
+ (`%LOCALAPPDATA%\TextFlowKit\ui` on Windows) only when they are unset, so an
578
+ explicit environment override is honoured unchanged.
579
+
508
580
  ## Durable job state
509
581
 
510
582
  By default jobs live in memory and are lost when the process exits. Set
@@ -7,18 +7,60 @@ current source paths noted throughout.
7
7
 
8
8
  ## One core, several doors
9
9
 
10
- TextFlowKit is a self-hosted media transcription library with a CLI and two
11
- adapters (MCP and a JSON HTTP API). Native Windows is the primary local target,
10
+ TextFlowKit is a self-hosted media transcription library with a CLI, two adapters
11
+ (MCP and a JSON HTTP API), and a local browser interface. Native Windows is the primary local target,
12
12
  including AMD ROCm through PyTorch for the explicit `whisper` engine; no WSL layer
13
13
  is involved. Linux and macOS are additional targets. The published website is
14
14
  static documentation and never runs the pipeline.
15
15
 
16
- ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](assets/architecture-overview.svg)
16
+ ![TextFlowKit shared-core architecture: five thin entry points feed one core. The CLI, Python API, MCP, HTTP API, and the local browser UI (a fifth door that mounts the existing HTTP app under /api and shares the same job store, adding transport only, not a new pipeline) sit above one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](assets/architecture-overview.svg)
17
17
 
18
18
  As of v0.1.9 the default engine is **Whistle**, a CPU-only native CLI that needs
19
19
  no torch and downloads one pinned model on first use. `openai-whisper` is an
20
20
  explicit opt-in engine (`--engine whisper`), not the default.
21
21
 
22
+ A **local browser interface** (`textflowkit-ui`) is a further door alongside the
23
+ CLI, MCP, and HTTP entry points. It mounts the existing developer HTTP app under
24
+ `/api` unchanged and adds only transport: a loopback-only browser session layer,
25
+ a streamed upload, and a capability endpoint. It **ships in the core package as
26
+ of v0.1.10** and adds no new dependency beyond the `http` extra. It is a local
27
+ app: the published website is static documentation and never runs the pipeline.
28
+
29
+ ### One owner per database
30
+
31
+ The durable SQLite store is owned by exactly one process. `textflowkit-ui`
32
+ enforces that with an exclusive owner lock (`ui/ownership.py`) keyed by the
33
+ **resolved** database path and taken *before* startup recovery runs. The lock is a
34
+ genuine operating-system file lock (`msvcrt` on Windows, `flock` on POSIX) on a
35
+ small sibling file that is created once and **never unlinked**, so there is no
36
+ unlink/recreate window in which two processes could lock different inodes at one
37
+ path. The kernel holds the lock for the live holder and releases it on exit *or
38
+ crash*, so a run that was killed does not block the next launch. A second launch
39
+ against a live owner does not start a competing server: it discovers the running
40
+ instance's loopback URL from a small owner record written beside the lock and
41
+ opens that instead. Ownership is proved by the held lock plus a matching owner
42
+ record — never by signalling or inspecting a pid. A leftover URL record with no
43
+ held lock is not treated as a live instance. Nothing here signals, stops, or
44
+ cleans up any other process — the documented rule is simply **do not point two
45
+ processes at one database file.**
46
+
47
+ Readiness is a property of the process, not a probe: the launcher reserves the
48
+ listening socket itself and drives uvicorn with `Server.serve(sockets=[...])`, so
49
+ the browser is opened only after *our* server is serving, and an unrelated
50
+ service holding the preferred port number is passed over for a genuinely free
51
+ one.
52
+
53
+ A **controlled shutdown** (`POST /ui/shutdown`, gated like every mutator by the
54
+ loopback origin gate and the session capability header, and confirmed in the
55
+ browser) asks this process's own uvicorn server to exit through its supported
56
+ `should_exit` flag. The server then runs its normal lifespan teardown, which
57
+ waits for the current jobs to finish before draining the worker pool rather than
58
+ interrupting them, so a running job's saved progress survives and is resumable.
59
+ It does not cancel an in-flight job; cancel it first for that. It never sends a
60
+ signal to a pid. On a console
61
+ (`pythonw`) launch the launcher keeps its own log file and reports a startup
62
+ failure in a message box, so a shortcut that fails to start is not a silent exit.
63
+
22
64
  One shared submission contract (`core/submission.py`) owns job creation and
23
65
  resume for every door. The CLI runs the pipeline synchronously; MCP and HTTP
24
66
  enqueue work and return a job handle. The public Python `transcribe()` calls the
@@ -30,6 +72,7 @@ flowchart LR
30
72
  CLI[CLI single and batch] --> SUB[core.submission]
31
73
  MCP[MCP stdio or Streamable HTTP] --> SUB
32
74
  HTTP[JSON HTTP adapter] --> SUB
75
+ UI[Local browser UI over /api] --> SUB
33
76
  SUB --> STORE[Memory store or SQLite WAL]
34
77
  SUB --> EXEC[In-process bounded executor]
35
78
  SUB --> SYNC[Synchronous inline run]