stemma-studio 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. stemma_studio-0.4.0/LICENSE +21 -0
  2. stemma_studio-0.4.0/PKG-INFO +276 -0
  3. stemma_studio-0.4.0/README.md +256 -0
  4. stemma_studio-0.4.0/pyproject.toml +63 -0
  5. stemma_studio-0.4.0/setup.cfg +4 -0
  6. stemma_studio-0.4.0/src/stemma_studio/__init__.py +10 -0
  7. stemma_studio-0.4.0/src/stemma_studio/blog/__init__.py +1 -0
  8. stemma_studio-0.4.0/src/stemma_studio/blog/admin.py +1604 -0
  9. stemma_studio-0.4.0/src/stemma_studio/blog/admin_frontend/admin.css +115 -0
  10. stemma_studio-0.4.0/src/stemma_studio/blog/admin_frontend/admin.js +668 -0
  11. stemma_studio-0.4.0/src/stemma_studio/blog/admin_frontend/buffer.js +25 -0
  12. stemma_studio-0.4.0/src/stemma_studio/blog/admin_frontend/flow.js +325 -0
  13. stemma_studio-0.4.0/src/stemma_studio/blog/admin_frontend/i18n.js +28 -0
  14. stemma_studio-0.4.0/src/stemma_studio/blog/admin_frontend/index.html +134 -0
  15. stemma_studio-0.4.0/src/stemma_studio/blog/admin_server.py +273 -0
  16. stemma_studio-0.4.0/src/stemma_studio/blog/attachments.py +57 -0
  17. stemma_studio-0.4.0/src/stemma_studio/blog/author_preview.py +139 -0
  18. stemma_studio-0.4.0/src/stemma_studio/blog/deploy.py +305 -0
  19. stemma_studio-0.4.0/src/stemma_studio/blog/footnotes.py +56 -0
  20. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/fonts/MaruBuri-Bold.woff2 +0 -0
  21. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/fonts/MaruBuri-Regular.woff2 +0 -0
  22. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/fonts/OFL.txt +114 -0
  23. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/fonts/README.md +11 -0
  24. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/site.css +60 -0
  25. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/site.js +110 -0
  26. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/studio-header.css +15 -0
  27. stemma_studio-0.4.0/src/stemma_studio/blog/frontend/theme.css +4 -0
  28. stemma_studio-0.4.0/src/stemma_studio/blog/homepage.py +344 -0
  29. stemma_studio-0.4.0/src/stemma_studio/blog/input.py +139 -0
  30. stemma_studio-0.4.0/src/stemma_studio/blog/local_folder.py +226 -0
  31. stemma_studio-0.4.0/src/stemma_studio/blog/markdown.py +115 -0
  32. stemma_studio-0.4.0/src/stemma_studio/blog/math-render.cjs +14 -0
  33. stemma_studio-0.4.0/src/stemma_studio/blog/math_support.py +60 -0
  34. stemma_studio-0.4.0/src/stemma_studio/blog/preview.py +153 -0
  35. stemma_studio-0.4.0/src/stemma_studio/blog/render.py +449 -0
  36. stemma_studio-0.4.0/src/stemma_studio/blog/sample/public-v3.json +347 -0
  37. stemma_studio-0.4.0/src/stemma_studio/blog/site_settings.py +172 -0
  38. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/KATEX-LICENSE +21 -0
  39. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/MISTUNE-LICENSE +14 -0
  40. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/README.md +14 -0
  41. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/__init__.py +1 -0
  42. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/katex/katex.cjs +17741 -0
  43. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/__init__.py +81 -0
  44. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/__main__.py +124 -0
  45. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/block_parser.py +486 -0
  46. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/core.py +209 -0
  47. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/__init__.py +31 -0
  48. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/_base.py +121 -0
  49. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/_fenced.py +142 -0
  50. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/_rst.py +73 -0
  51. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/admonition.py +61 -0
  52. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/image.py +152 -0
  53. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/include.py +65 -0
  54. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/directives/toc.py +105 -0
  55. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/helpers.py +137 -0
  56. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/inline_parser.py +390 -0
  57. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/list_parser.py +254 -0
  58. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/markdown.py +110 -0
  59. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/__init__.py +38 -0
  60. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/abbr.py +103 -0
  61. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/def_list.py +135 -0
  62. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/footnotes.py +153 -0
  63. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/formatting.py +173 -0
  64. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/math.py +57 -0
  65. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/ruby.py +100 -0
  66. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/speedup.py +44 -0
  67. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/spoiler.py +80 -0
  68. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/table.py +179 -0
  69. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/task_lists.py +67 -0
  70. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/plugins/url.py +23 -0
  71. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/renderers/__init__.py +0 -0
  72. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/renderers/_list.py +60 -0
  73. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/renderers/html.py +151 -0
  74. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/renderers/markdown.py +146 -0
  75. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/renderers/rst.py +147 -0
  76. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/toc.py +111 -0
  77. stemma_studio-0.4.0/src/stemma_studio/blog/vendor/mistune/util.py +81 -0
  78. stemma_studio-0.4.0/src/stemma_studio/cli.py +178 -0
  79. stemma_studio-0.4.0/src/stemma_studio/core/__init__.py +1 -0
  80. stemma_studio-0.4.0/src/stemma_studio/core/__main__.py +175 -0
  81. stemma_studio-0.4.0/src/stemma_studio/core/adapters.py +33 -0
  82. stemma_studio-0.4.0/src/stemma_studio/core/application.py +357 -0
  83. stemma_studio-0.4.0/src/stemma_studio/core/archive_sync.py +305 -0
  84. stemma_studio-0.4.0/src/stemma_studio/core/blog.py +313 -0
  85. stemma_studio-0.4.0/src/stemma_studio/core/containment.py +50 -0
  86. stemma_studio-0.4.0/src/stemma_studio/core/disclosure.py +190 -0
  87. stemma_studio-0.4.0/src/stemma_studio/core/domain.py +221 -0
  88. stemma_studio-0.4.0/src/stemma_studio/core/editing.py +65 -0
  89. stemma_studio-0.4.0/src/stemma_studio/core/folder_import.py +353 -0
  90. stemma_studio-0.4.0/src/stemma_studio/core/migrations.py +59 -0
  91. stemma_studio-0.4.0/src/stemma_studio/core/public_assets.py +57 -0
  92. stemma_studio-0.4.0/src/stemma_studio/core/publication.py +194 -0
  93. stemma_studio-0.4.0/src/stemma_studio/core/releases.py +168 -0
  94. stemma_studio-0.4.0/src/stemma_studio/core/repository.py +196 -0
  95. stemma_studio-0.4.0/src/stemma_studio/core/uploads.py +158 -0
  96. stemma_studio-0.4.0/src/stemma_studio/editor/__init__.py +1 -0
  97. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/app.js +495 -0
  98. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/feedback.js +60 -0
  99. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/genealogy-layout.js +46 -0
  100. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/genealogy.css +3 -0
  101. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/genealogy.html +10 -0
  102. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/genealogy.js +106 -0
  103. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/i18n.js +28 -0
  104. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/index.html +18 -0
  105. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/style.css +19 -0
  106. stemma_studio-0.4.0/src/stemma_studio/editor/frontend/work-buffer.js +42 -0
  107. stemma_studio-0.4.0/src/stemma_studio/editor/server.py +221 -0
  108. stemma_studio-0.4.0/src/stemma_studio/editor/service.py +424 -0
  109. stemma_studio-0.4.0/src/stemma_studio/editor/workspace_server.py +67 -0
  110. stemma_studio-0.4.0/src/stemma_studio/locale/__init__.py +159 -0
  111. stemma_studio-0.4.0/src/stemma_studio/locale/en.json +697 -0
  112. stemma_studio-0.4.0/src/stemma_studio/locale/ko.json +697 -0
  113. stemma_studio-0.4.0/src/stemma_studio.egg-info/PKG-INFO +276 -0
  114. stemma_studio-0.4.0/src/stemma_studio.egg-info/SOURCES.txt +141 -0
  115. stemma_studio-0.4.0/src/stemma_studio.egg-info/dependency_links.txt +1 -0
  116. stemma_studio-0.4.0/src/stemma_studio.egg-info/entry_points.txt +2 -0
  117. stemma_studio-0.4.0/src/stemma_studio.egg-info/requires.txt +4 -0
  118. stemma_studio-0.4.0/src/stemma_studio.egg-info/top_level.txt +1 -0
  119. stemma_studio-0.4.0/tests/test_archive_sync.py +330 -0
  120. stemma_studio-0.4.0/tests/test_archiving.py +227 -0
  121. stemma_studio-0.4.0/tests/test_blog_admin.py +812 -0
  122. stemma_studio-0.4.0/tests/test_blog_attachments.py +107 -0
  123. stemma_studio-0.4.0/tests/test_blog_contract.py +208 -0
  124. stemma_studio-0.4.0/tests/test_blog_footnotes.py +45 -0
  125. stemma_studio-0.4.0/tests/test_blog_model.py +356 -0
  126. stemma_studio-0.4.0/tests/test_blog_release.py +255 -0
  127. stemma_studio-0.4.0/tests/test_blog_site.py +355 -0
  128. stemma_studio-0.4.0/tests/test_blog_typography.py +182 -0
  129. stemma_studio-0.4.0/tests/test_containment.py +56 -0
  130. stemma_studio-0.4.0/tests/test_editor.py +418 -0
  131. stemma_studio-0.4.0/tests/test_folder_import.py +368 -0
  132. stemma_studio-0.4.0/tests/test_graph_core.py +158 -0
  133. stemma_studio-0.4.0/tests/test_homepage.py +154 -0
  134. stemma_studio-0.4.0/tests/test_interface_access.py +63 -0
  135. stemma_studio-0.4.0/tests/test_local_folder.py +569 -0
  136. stemma_studio-0.4.0/tests/test_locale.py +199 -0
  137. stemma_studio-0.4.0/tests/test_model.py +197 -0
  138. stemma_studio-0.4.0/tests/test_package_layout.py +198 -0
  139. stemma_studio-0.4.0/tests/test_publication_v3.py +355 -0
  140. stemma_studio-0.4.0/tests/test_site_settings.py +214 -0
  141. stemma_studio-0.4.0/tests/test_uploads.py +259 -0
  142. stemma_studio-0.4.0/tests/test_workspace_server.py +111 -0
  143. stemma_studio-0.4.0/tests/test_writing_language.py +277 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Byeongsu Yu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,276 @@
1
+ Metadata-Version: 2.4
2
+ Name: stemma-studio
3
+ Version: 0.4.0
4
+ Summary: Writing that descends from earlier writing: a local desk, a genealogy of versions, and a bilingual static blog
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/byeongsuyu/stemma
7
+ Project-URL: Source, https://github.com/byeongsuyu/stemma
8
+ Keywords: writing,blog,static-site,genealogy,stemma,markdown
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: End Users/Desktop
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
13
+ Requires-Python: >=3.12
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: stemma-graph<0.4.0,>=0.3.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: ruff>=0.6; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # Stemma
22
+
23
+ **English** · [한국어](https://github.com/byeongsuyu/stemma/blob/main/README.ko.md)
24
+
25
+ A local writing desk for people who rewrite. You keep a private archive of
26
+ everything you have written, find an old piece, write a new one that carries it
27
+ forward, and publish the versions you choose as a bilingual static blog — with
28
+ the lineage from the old piece to the new one preserved and, if you want,
29
+ visible to readers.
30
+
31
+ It runs entirely on your own machine. Nothing is uploaded except the static
32
+ site you explicitly deploy.
33
+
34
+ > A *stemma codicum* is the diagram textual scholars draw to show which
35
+ > manuscript descends from which. This tool builds one for your own writing.
36
+
37
+ > The interface speaks Korean or English — switch it from the top bar. The
38
+ > documentation, the code and the command line are in English. Published sites
39
+ > are Korean/English pairs: you write in one of the two languages and translate
40
+ > into the other.
41
+
42
+ ## What makes it different
43
+
44
+ Most blogging tools treat a post as a standalone artifact. Stemma treats it as
45
+ a **succession**: this piece came out of that one, in answer to a particular
46
+ question. That relationship is first-class data.
47
+
48
+ - **Questions** are what you are asking. A document belongs to one or more.
49
+ - **Succession edges** record that one document grew out of another.
50
+ - **Revisions** are immutable. Editing never overwrites what you wrote before.
51
+ - **Publication pairs** are a reviewed Korean + English version of a document:
52
+ the revision in the language you write in, and its translation. You publish a
53
+ pair, never a bare revision.
54
+ - **Genealogy** is the resulting graph, which readers can explore on the
55
+ published site.
56
+
57
+ ## Requirements
58
+
59
+ - Python 3.12 or newer. The core, the editor and the blog use only the standard
60
+ library.
61
+ - Node.js, **only** if you publish posts containing mathematics. KaTeX renders
62
+ formulas to static MathML at build time; readers never run it. See
63
+ `src/stemma_studio/blog/math_support.py`.
64
+ - Git, only if you import from a Git archive or deploy through a pull request.
65
+ Publishing to a folder needs neither.
66
+
67
+ ## Install
68
+
69
+ ```sh
70
+ python3.12 -m venv .venv
71
+ .venv/bin/pip install ./packages/stemma_graph .
72
+ ```
73
+
74
+ That gives you the `stemma` command. Once the packages are published, this
75
+ becomes `pip install stemma-studio`, which will pull in `stemma-graph` with it;
76
+ until then, install from a checkout as above.
77
+
78
+ To work on the code, you can run everything from a checkout with no install at
79
+ all:
80
+
81
+ ```sh
82
+ python3.12 -B scripts/dev.py test # the Python suite
83
+ python3.12 -B scripts/dev.py example # the genealogy library on its own
84
+ ```
85
+
86
+ ## Quick start
87
+
88
+ ```sh
89
+ mkdir my-writing && cd my-writing
90
+ stemma init # creates data/studio.json here
91
+ stemma editor # http://127.0.0.1:8787
92
+ ```
93
+
94
+ `init` creates an empty data root in the current folder. Every command takes
95
+ `--root` if you want to keep your data somewhere else; without it, the current
96
+ working directory is the root.
97
+
98
+ To see what a published site looks like before you have written anything, run
99
+ `stemma preview` and open <http://127.0.0.1:8766/blog/>. It serves a
100
+ synthetic sample from memory and writes nothing.
101
+
102
+ ## The workflow
103
+
104
+ Everything happens on one local server, in four steps:
105
+
106
+ | | Step | Where |
107
+ |---|---|---|
108
+ | 1 | **Set up your blog** — its name, byline, the language you write in and the licence on your writing | Settings screen at `/publish/?view=settings` |
109
+ | 2 | **Import** what you have already written, then **write** | Import, then the editor at `/` |
110
+ | 3 | **Publish** — add the translation, review the pair, confirm it | Publication screen at `/publish/` |
111
+ | 4 | **Deploy** — write the site to a folder, or open one pull request | Site screen at `/publish/?view=site` |
112
+
113
+ Then read it on your own site. Step 1 is worth doing first: until you do, pages
114
+ publish under placeholder names, your writing reserves all rights by default,
115
+ and a new data root cannot publish at all until you have said which language
116
+ you write in.
117
+
118
+ ## Setting up your blog
119
+
120
+ Your blog's name, byline and tagline, the language you write in, and the
121
+ licence your writing carries, are configuration rather than code — one installed
122
+ copy can publish anybody's blog. Set them on the **Settings** screen under
123
+ **Blog settings**, which writes `data/blog/site.json`.
124
+
125
+ The language you write in decides which side of each pair is the original. Write
126
+ in Korean and you add an English translation; write in English and you add a
127
+ Korean one. A post keeps the side it was first published or translated from, so
128
+ changing this later never turns round a post you have already started. That screen holds what you decide once — the blog's
129
+ identity and where it deploys to — apart from the site screen, which is what you
130
+ check each time you publish.
131
+
132
+ Note that two different licences meet on a published page:
133
+
134
+ - **This tool is MIT.** See [LICENSE](https://github.com/byeongsuyu/stemma/blob/main/LICENSE). That covers the software.
135
+ - **Your writing is yours.** Its terms are a setting, defaulting to reserving
136
+ all rights. Presets for the Creative Commons licences and CC0 are offered,
137
+ or you can write your own wording. Nothing gives your readers rights you did
138
+ not choose.
139
+
140
+ See [docs/site-settings.md](https://github.com/byeongsuyu/stemma/blob/main/docs/site-settings.md).
141
+
142
+ ## Writing
143
+
144
+ The editor at <http://127.0.0.1:8787> has three panes: search your archive,
145
+ read the sources you picked, and write. When you save, Stemma stores an
146
+ immutable revision and asks which parent documents this one carries forward,
147
+ and under which question.
148
+
149
+ **Add pictures or files** attaches pictures — several at once — storing each under its own
150
+ content hash and writing the Markdown link where your cursor is.
151
+
152
+ The same server hosts the publication screens at `/publish/`, where you add the
153
+ translation of a post, review the pair, and confirm it for publication. The
154
+ `/publish/?view=site` screen shows everything that would change on the live
155
+ site and deploys it.
156
+
157
+ The model's own workflow is available on the command line:
158
+
159
+ | Command | What it does |
160
+ |---|---|
161
+ | `create` | Start a draft, with repeated `--parent` and `--question` |
162
+ | `revise` | Store an input file as a new immutable revision |
163
+ | `confirm` | Confirm a document and its proposed successions |
164
+ | `archive` / `restore` | Retire a document from future use, or bring it back |
165
+ | `represent` | Choose which documents represent a question |
166
+ | `publish --pair` | Select a reviewed Korean/English pair locally |
167
+ | `unpublish` | Clear that selection |
168
+ | `export-public --planned-at` | Print the prospective public payload as JSON |
169
+ | `validate` | Check metadata and content hashes |
170
+
171
+ Run `stemma <command> --help` for the arguments. `publish` records a
172
+ local choice; it never uploads anything. Deployment is a separate, explicit
173
+ step. Global options come before the subcommand: `stemma --root . validate`.
174
+
175
+ Some work happens only in the editor and publication screens: writing a
176
+ question, preparing and reviewing a translation pair, attaching a file, and
177
+ importing a folder. `create` needs a question that already exists, so a fresh
178
+ data root is set up through the editor rather than the command line alone.
179
+
180
+ ## Importing your existing writing
181
+
182
+ Open **Import** and give it the folder your writing lives in. It reads the
183
+ folder — never modifies it — and shows exactly what it would import before
184
+ anything is saved.
185
+
186
+ **Picture links are not rewritten.** A picture is recorded at the path it
187
+ occupies relative to the post that references it, so `![](images/cat.png)` and
188
+ `![](../shared/logo.png)` keep working as written. That is why it takes a
189
+ folder: the folder's shape is what makes the links resolve.
190
+
191
+ If your writing lives in a Git repository you keep updating, a second route
192
+ tracks it by commit:
193
+
194
+ ```sh
195
+ stemma sync-archive --archive /absolute/path/to/your-archive
196
+ stemma sync-archive --dry-run # later runs remember the path
197
+ ```
198
+
199
+ Both are read-only and append-only. See [docs/importing.md](https://github.com/byeongsuyu/stemma/blob/main/docs/importing.md).
200
+
201
+ Already have writing in a Stemma data root? Do not import it — point the tool
202
+ at it: `stemma --root /path/containing/data editor`. See
203
+ [docs/moving-in.md](https://github.com/byeongsuyu/stemma/blob/main/docs/moving-in.md).
204
+
205
+ ## Publishing
206
+
207
+ Deployment builds the static site and sends it to one destination, chosen on
208
+ the site screen:
209
+
210
+ - **A local folder** (default) — writes the finished site to a folder you name.
211
+ No account and nothing to wait for. Move that folder wherever you like: any
212
+ static host, `rsync`, an object store, a USB stick. Files this tool did not
213
+ write are never touched.
214
+ - **A GitHub Pages pull request** — opens a PR against a Pages repository you
215
+ control, so you see the diff before it goes live. Needs the `gh` CLI.
216
+
217
+ The folder is the general case and the reason the tool is not tied to one
218
+ service. See [docs/publishing.md](https://github.com/byeongsuyu/stemma/blob/main/docs/publishing.md).
219
+
220
+ ## Layout
221
+
222
+ ```
223
+ packages/stemma_graph/ pip: stemma-graph the genealogy graph, stdlib only
224
+ examples/ a runnable demo of the graph on its own
225
+ src/stemma_studio/ pip: stemma-studio installs the `stemma` command
226
+ core/ the model, storage, archive sync and publication rules
227
+ blog/ Markdown rendering, the reader's site, publication admin, deploy
228
+ editor/ the writing desk, which mounts the blog's publication screens
229
+ cli.py the stemma command
230
+ ```
231
+
232
+ The dependency direction runs one way — `core` imports neither sibling, and
233
+ `blog` never imports `editor`. A test enforces it. `stemma-graph` is a
234
+ separate package because it is useful on its own and depends on nothing; you
235
+ can install it alone if all you want is the graph.
236
+
237
+ Neither distribution needs a third-party package installed alongside it, beyond
238
+ Studio's dependency on `stemma-graph`. Studio is not pure standard library
239
+ though: it ships vendored copies of mistune and KaTeX inside the wheel, under
240
+ their own licences.
241
+
242
+ Your writing lives in the data root, never in the package: `data/studio.json`
243
+ holds the metadata, with preserved copies under `imports/`, `revisions/`,
244
+ `assets/` and `translations/`. No personal data is included in either wheel.
245
+
246
+ ## Documentation
247
+
248
+ | Document | Read it for |
249
+ |---|---|
250
+ | [Design](https://github.com/byeongsuyu/stemma/blob/main/docs/design.md) | The rules for documents, succession, retirement and publication, and why they hold |
251
+ | [Code tour](https://github.com/byeongsuyu/stemma/blob/main/docs/code-tour.md) | Where code lives, entry points, and how to run it |
252
+ | [Editor](https://github.com/byeongsuyu/stemma/blob/main/docs/editor.md) | The writing desk, and its save and recovery boundaries |
253
+ | [Publishing](https://github.com/byeongsuyu/stemma/blob/main/docs/publishing.md) | Bilingual publication, releases and deployment |
254
+ | [Importing](https://github.com/byeongsuyu/stemma/blob/main/docs/importing.md) | Bringing your existing writing in, by folder or from Git |
255
+ | [Site settings](https://github.com/byeongsuyu/stemma/blob/main/docs/site-settings.md) | Naming your blog, and licensing your writing |
256
+ | [Moving in](https://github.com/byeongsuyu/stemma/blob/main/docs/moving-in.md) | Bringing an existing data root into a new install |
257
+ | [Contributing](https://github.com/byeongsuyu/stemma/blob/main/CONTRIBUTING.md) | Tests, style and the rules a change must keep |
258
+
259
+ ## Status
260
+
261
+ This is one person's tool, published in case it is useful to others. It assumes
262
+ a single local author and does not enforce that assumption: each running server
263
+ serializes its own requests, but nothing coordinates between two servers, or
264
+ between a server and the command line. Do not run two of them against one data
265
+ root.
266
+
267
+ ## License
268
+
269
+ The **code** is MIT — see [LICENSE](https://github.com/byeongsuyu/stemma/blob/main/LICENSE). Bundled third-party code keeps
270
+ its own licence: mistune (BSD-3-Clause), KaTeX (MIT) and the MaruBuri font
271
+ (SIL OFL 1.1). See `src/stemma_studio/blog/vendor/README.md` and
272
+ `src/stemma_studio/blog/frontend/fonts/README.md`.
273
+
274
+ The **writing** you publish with it is not covered by that licence. Its terms
275
+ are yours to choose, in the blog settings; see
276
+ [docs/site-settings.md](https://github.com/byeongsuyu/stemma/blob/main/docs/site-settings.md).
@@ -0,0 +1,256 @@
1
+ # Stemma
2
+
3
+ **English** · [한국어](https://github.com/byeongsuyu/stemma/blob/main/README.ko.md)
4
+
5
+ A local writing desk for people who rewrite. You keep a private archive of
6
+ everything you have written, find an old piece, write a new one that carries it
7
+ forward, and publish the versions you choose as a bilingual static blog — with
8
+ the lineage from the old piece to the new one preserved and, if you want,
9
+ visible to readers.
10
+
11
+ It runs entirely on your own machine. Nothing is uploaded except the static
12
+ site you explicitly deploy.
13
+
14
+ > A *stemma codicum* is the diagram textual scholars draw to show which
15
+ > manuscript descends from which. This tool builds one for your own writing.
16
+
17
+ > The interface speaks Korean or English — switch it from the top bar. The
18
+ > documentation, the code and the command line are in English. Published sites
19
+ > are Korean/English pairs: you write in one of the two languages and translate
20
+ > into the other.
21
+
22
+ ## What makes it different
23
+
24
+ Most blogging tools treat a post as a standalone artifact. Stemma treats it as
25
+ a **succession**: this piece came out of that one, in answer to a particular
26
+ question. That relationship is first-class data.
27
+
28
+ - **Questions** are what you are asking. A document belongs to one or more.
29
+ - **Succession edges** record that one document grew out of another.
30
+ - **Revisions** are immutable. Editing never overwrites what you wrote before.
31
+ - **Publication pairs** are a reviewed Korean + English version of a document:
32
+ the revision in the language you write in, and its translation. You publish a
33
+ pair, never a bare revision.
34
+ - **Genealogy** is the resulting graph, which readers can explore on the
35
+ published site.
36
+
37
+ ## Requirements
38
+
39
+ - Python 3.12 or newer. The core, the editor and the blog use only the standard
40
+ library.
41
+ - Node.js, **only** if you publish posts containing mathematics. KaTeX renders
42
+ formulas to static MathML at build time; readers never run it. See
43
+ `src/stemma_studio/blog/math_support.py`.
44
+ - Git, only if you import from a Git archive or deploy through a pull request.
45
+ Publishing to a folder needs neither.
46
+
47
+ ## Install
48
+
49
+ ```sh
50
+ python3.12 -m venv .venv
51
+ .venv/bin/pip install ./packages/stemma_graph .
52
+ ```
53
+
54
+ That gives you the `stemma` command. Once the packages are published, this
55
+ becomes `pip install stemma-studio`, which will pull in `stemma-graph` with it;
56
+ until then, install from a checkout as above.
57
+
58
+ To work on the code, you can run everything from a checkout with no install at
59
+ all:
60
+
61
+ ```sh
62
+ python3.12 -B scripts/dev.py test # the Python suite
63
+ python3.12 -B scripts/dev.py example # the genealogy library on its own
64
+ ```
65
+
66
+ ## Quick start
67
+
68
+ ```sh
69
+ mkdir my-writing && cd my-writing
70
+ stemma init # creates data/studio.json here
71
+ stemma editor # http://127.0.0.1:8787
72
+ ```
73
+
74
+ `init` creates an empty data root in the current folder. Every command takes
75
+ `--root` if you want to keep your data somewhere else; without it, the current
76
+ working directory is the root.
77
+
78
+ To see what a published site looks like before you have written anything, run
79
+ `stemma preview` and open <http://127.0.0.1:8766/blog/>. It serves a
80
+ synthetic sample from memory and writes nothing.
81
+
82
+ ## The workflow
83
+
84
+ Everything happens on one local server, in four steps:
85
+
86
+ | | Step | Where |
87
+ |---|---|---|
88
+ | 1 | **Set up your blog** — its name, byline, the language you write in and the licence on your writing | Settings screen at `/publish/?view=settings` |
89
+ | 2 | **Import** what you have already written, then **write** | Import, then the editor at `/` |
90
+ | 3 | **Publish** — add the translation, review the pair, confirm it | Publication screen at `/publish/` |
91
+ | 4 | **Deploy** — write the site to a folder, or open one pull request | Site screen at `/publish/?view=site` |
92
+
93
+ Then read it on your own site. Step 1 is worth doing first: until you do, pages
94
+ publish under placeholder names, your writing reserves all rights by default,
95
+ and a new data root cannot publish at all until you have said which language
96
+ you write in.
97
+
98
+ ## Setting up your blog
99
+
100
+ Your blog's name, byline and tagline, the language you write in, and the
101
+ licence your writing carries, are configuration rather than code — one installed
102
+ copy can publish anybody's blog. Set them on the **Settings** screen under
103
+ **Blog settings**, which writes `data/blog/site.json`.
104
+
105
+ The language you write in decides which side of each pair is the original. Write
106
+ in Korean and you add an English translation; write in English and you add a
107
+ Korean one. A post keeps the side it was first published or translated from, so
108
+ changing this later never turns round a post you have already started. That screen holds what you decide once — the blog's
109
+ identity and where it deploys to — apart from the site screen, which is what you
110
+ check each time you publish.
111
+
112
+ Note that two different licences meet on a published page:
113
+
114
+ - **This tool is MIT.** See [LICENSE](https://github.com/byeongsuyu/stemma/blob/main/LICENSE). That covers the software.
115
+ - **Your writing is yours.** Its terms are a setting, defaulting to reserving
116
+ all rights. Presets for the Creative Commons licences and CC0 are offered,
117
+ or you can write your own wording. Nothing gives your readers rights you did
118
+ not choose.
119
+
120
+ See [docs/site-settings.md](https://github.com/byeongsuyu/stemma/blob/main/docs/site-settings.md).
121
+
122
+ ## Writing
123
+
124
+ The editor at <http://127.0.0.1:8787> has three panes: search your archive,
125
+ read the sources you picked, and write. When you save, Stemma stores an
126
+ immutable revision and asks which parent documents this one carries forward,
127
+ and under which question.
128
+
129
+ **Add pictures or files** attaches pictures — several at once — storing each under its own
130
+ content hash and writing the Markdown link where your cursor is.
131
+
132
+ The same server hosts the publication screens at `/publish/`, where you add the
133
+ translation of a post, review the pair, and confirm it for publication. The
134
+ `/publish/?view=site` screen shows everything that would change on the live
135
+ site and deploys it.
136
+
137
+ The model's own workflow is available on the command line:
138
+
139
+ | Command | What it does |
140
+ |---|---|
141
+ | `create` | Start a draft, with repeated `--parent` and `--question` |
142
+ | `revise` | Store an input file as a new immutable revision |
143
+ | `confirm` | Confirm a document and its proposed successions |
144
+ | `archive` / `restore` | Retire a document from future use, or bring it back |
145
+ | `represent` | Choose which documents represent a question |
146
+ | `publish --pair` | Select a reviewed Korean/English pair locally |
147
+ | `unpublish` | Clear that selection |
148
+ | `export-public --planned-at` | Print the prospective public payload as JSON |
149
+ | `validate` | Check metadata and content hashes |
150
+
151
+ Run `stemma <command> --help` for the arguments. `publish` records a
152
+ local choice; it never uploads anything. Deployment is a separate, explicit
153
+ step. Global options come before the subcommand: `stemma --root . validate`.
154
+
155
+ Some work happens only in the editor and publication screens: writing a
156
+ question, preparing and reviewing a translation pair, attaching a file, and
157
+ importing a folder. `create` needs a question that already exists, so a fresh
158
+ data root is set up through the editor rather than the command line alone.
159
+
160
+ ## Importing your existing writing
161
+
162
+ Open **Import** and give it the folder your writing lives in. It reads the
163
+ folder — never modifies it — and shows exactly what it would import before
164
+ anything is saved.
165
+
166
+ **Picture links are not rewritten.** A picture is recorded at the path it
167
+ occupies relative to the post that references it, so `![](images/cat.png)` and
168
+ `![](../shared/logo.png)` keep working as written. That is why it takes a
169
+ folder: the folder's shape is what makes the links resolve.
170
+
171
+ If your writing lives in a Git repository you keep updating, a second route
172
+ tracks it by commit:
173
+
174
+ ```sh
175
+ stemma sync-archive --archive /absolute/path/to/your-archive
176
+ stemma sync-archive --dry-run # later runs remember the path
177
+ ```
178
+
179
+ Both are read-only and append-only. See [docs/importing.md](https://github.com/byeongsuyu/stemma/blob/main/docs/importing.md).
180
+
181
+ Already have writing in a Stemma data root? Do not import it — point the tool
182
+ at it: `stemma --root /path/containing/data editor`. See
183
+ [docs/moving-in.md](https://github.com/byeongsuyu/stemma/blob/main/docs/moving-in.md).
184
+
185
+ ## Publishing
186
+
187
+ Deployment builds the static site and sends it to one destination, chosen on
188
+ the site screen:
189
+
190
+ - **A local folder** (default) — writes the finished site to a folder you name.
191
+ No account and nothing to wait for. Move that folder wherever you like: any
192
+ static host, `rsync`, an object store, a USB stick. Files this tool did not
193
+ write are never touched.
194
+ - **A GitHub Pages pull request** — opens a PR against a Pages repository you
195
+ control, so you see the diff before it goes live. Needs the `gh` CLI.
196
+
197
+ The folder is the general case and the reason the tool is not tied to one
198
+ service. See [docs/publishing.md](https://github.com/byeongsuyu/stemma/blob/main/docs/publishing.md).
199
+
200
+ ## Layout
201
+
202
+ ```
203
+ packages/stemma_graph/ pip: stemma-graph the genealogy graph, stdlib only
204
+ examples/ a runnable demo of the graph on its own
205
+ src/stemma_studio/ pip: stemma-studio installs the `stemma` command
206
+ core/ the model, storage, archive sync and publication rules
207
+ blog/ Markdown rendering, the reader's site, publication admin, deploy
208
+ editor/ the writing desk, which mounts the blog's publication screens
209
+ cli.py the stemma command
210
+ ```
211
+
212
+ The dependency direction runs one way — `core` imports neither sibling, and
213
+ `blog` never imports `editor`. A test enforces it. `stemma-graph` is a
214
+ separate package because it is useful on its own and depends on nothing; you
215
+ can install it alone if all you want is the graph.
216
+
217
+ Neither distribution needs a third-party package installed alongside it, beyond
218
+ Studio's dependency on `stemma-graph`. Studio is not pure standard library
219
+ though: it ships vendored copies of mistune and KaTeX inside the wheel, under
220
+ their own licences.
221
+
222
+ Your writing lives in the data root, never in the package: `data/studio.json`
223
+ holds the metadata, with preserved copies under `imports/`, `revisions/`,
224
+ `assets/` and `translations/`. No personal data is included in either wheel.
225
+
226
+ ## Documentation
227
+
228
+ | Document | Read it for |
229
+ |---|---|
230
+ | [Design](https://github.com/byeongsuyu/stemma/blob/main/docs/design.md) | The rules for documents, succession, retirement and publication, and why they hold |
231
+ | [Code tour](https://github.com/byeongsuyu/stemma/blob/main/docs/code-tour.md) | Where code lives, entry points, and how to run it |
232
+ | [Editor](https://github.com/byeongsuyu/stemma/blob/main/docs/editor.md) | The writing desk, and its save and recovery boundaries |
233
+ | [Publishing](https://github.com/byeongsuyu/stemma/blob/main/docs/publishing.md) | Bilingual publication, releases and deployment |
234
+ | [Importing](https://github.com/byeongsuyu/stemma/blob/main/docs/importing.md) | Bringing your existing writing in, by folder or from Git |
235
+ | [Site settings](https://github.com/byeongsuyu/stemma/blob/main/docs/site-settings.md) | Naming your blog, and licensing your writing |
236
+ | [Moving in](https://github.com/byeongsuyu/stemma/blob/main/docs/moving-in.md) | Bringing an existing data root into a new install |
237
+ | [Contributing](https://github.com/byeongsuyu/stemma/blob/main/CONTRIBUTING.md) | Tests, style and the rules a change must keep |
238
+
239
+ ## Status
240
+
241
+ This is one person's tool, published in case it is useful to others. It assumes
242
+ a single local author and does not enforce that assumption: each running server
243
+ serializes its own requests, but nothing coordinates between two servers, or
244
+ between a server and the command line. Do not run two of them against one data
245
+ root.
246
+
247
+ ## License
248
+
249
+ The **code** is MIT — see [LICENSE](https://github.com/byeongsuyu/stemma/blob/main/LICENSE). Bundled third-party code keeps
250
+ its own licence: mistune (BSD-3-Clause), KaTeX (MIT) and the MaruBuri font
251
+ (SIL OFL 1.1). See `src/stemma_studio/blog/vendor/README.md` and
252
+ `src/stemma_studio/blog/frontend/fonts/README.md`.
253
+
254
+ The **writing** you publish with it is not covered by that licence. Its terms
255
+ are yours to choose, in the blog settings; see
256
+ [docs/site-settings.md](https://github.com/byeongsuyu/stemma/blob/main/docs/site-settings.md).
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # The distribution is stemma-studio; the command it installs is `stemma`.
7
+ name = "stemma-studio"
8
+ version = "0.4.0"
9
+ description = "Writing that descends from earlier writing: a local desk, a genealogy of versions, and a bilingual static blog"
10
+ readme = "README.md"
11
+ requires-python = ">=3.12"
12
+ license = "MIT"
13
+ license-files = ["LICENSE"]
14
+ keywords = ["writing", "blog", "static-site", "genealogy", "stemma", "markdown"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Text Processing :: Markup :: Markdown",
20
+ ]
21
+ dependencies = ["stemma-graph>=0.3.0,<0.4.0"]
22
+
23
+ [project.optional-dependencies]
24
+ dev = ["ruff>=0.6"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/byeongsuyu/stemma"
28
+ Source = "https://github.com/byeongsuyu/stemma"
29
+
30
+ [project.scripts]
31
+ stemma = "stemma_studio.cli:main"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.setuptools.package-data]
37
+ "stemma_studio.blog" = [
38
+ "frontend/*.css", "frontend/*.js", "frontend/fonts/*.woff2", "frontend/fonts/*.txt",
39
+ "frontend/fonts/README.md", "admin_frontend/*.css", "admin_frontend/*.js",
40
+ "admin_frontend/*.html", "sample/*.json", "math-render.cjs",
41
+ "vendor/katex/*.cjs", "vendor/*LICENSE", "vendor/README.md",
42
+ ]
43
+ "stemma_studio.editor" = ["frontend/*.css", "frontend/*.js", "frontend/*.html"]
44
+ "stemma_studio.locale" = ["*.json"]
45
+
46
+ [tool.ruff]
47
+ line-length = 120
48
+ target-version = "py312"
49
+ src = ["src", "tests"]
50
+ extend-exclude = ["src/stemma_studio/blog/vendor"]
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E", "F", "W", "I", "UP", "B"]
54
+ ignore = [
55
+ "E501", # the formatter decides where lines wrap
56
+ "B023", # render_site's page helpers are called inside the same loop iteration
57
+ "B905", # adding strict= to zip would change behaviour, not just style
58
+ "E731", # the short assigned lambdas in the renderers read better than defs
59
+ ]
60
+
61
+ [tool.ruff.lint.per-file-ignores]
62
+ # This guard has to keep working on the older interpreter it warns about.
63
+ "scripts/dev.py" = ["UP036"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ """Stemma: a local writing desk, genealogy model and bilingual blog publisher.
2
+
3
+ The three subpackages depend in one direction only. ``core`` holds the model,
4
+ persistence and publication rules and imports neither sibling. ``blog`` renders
5
+ and deploys the public site on top of ``core``. ``editor`` serves the writing
6
+ desk and mounts the blog's publication screens. ``tests/test_package_layout.py``
7
+ enforces that direction.
8
+ """
9
+
10
+ __version__ = "0.4.0"
@@ -0,0 +1 @@
1
+ """Bilingual static blog: Markdown rendering, preview, publication admin and deploy."""