docsmith 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +260 -2
- data/CODE_OF_CONDUCT.md +6 -127
- data/README.md +35 -1
- data/USAGE.md +195 -22
- data/lib/docsmith/comments/anchor.rb +6 -1
- data/lib/docsmith/comments/comment.rb +9 -13
- data/lib/docsmith/configuration.rb +19 -2
- data/lib/docsmith/diff/parsers/html.rb +12 -45
- data/lib/docsmith/diff/parsers/markdown.rb +13 -40
- data/lib/docsmith/diff/renderers/base.rb +134 -34
- data/lib/docsmith/diff/result.rb +74 -29
- data/lib/docsmith/document_version.rb +15 -0
- data/lib/docsmith/errors.rb +10 -0
- data/lib/docsmith/rendering/html_renderer.rb +26 -1
- data/lib/docsmith/rendering/json_renderer.rb +64 -17
- data/lib/docsmith/version.rb +7 -1
- data/lib/generators/docsmith/install/install_generator.rb +23 -1
- data/lib/generators/docsmith/install/templates/create_docsmith_tables.rb.erb +3 -3
- data/lib/generators/docsmith/install/templates/docsmith_initializer.rb.erb +13 -1
- metadata +17 -9
- data/.rspec +0 -3
- data/.rspec_status +0 -212
- data/docs/superpowers/plans/2026-04-01-docsmith-full-plan.md +0 -6459
- data/docs/superpowers/plans/2026-04-08-parsers-remove-branches-docs.md +0 -2112
- data/docs/superpowers/specs/2026-04-01-docsmith-phase1-design.md +0 -340
- data/docsmith_spec.md +0 -630
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41620e26ac475ea46a8611b5aab7e808bff7a67e0246f9af0894c59164dd88b2
|
|
4
|
+
data.tar.gz: 89bb6a2920c4088f72de8b6c6c51d8e7aaffa6547fbaba815ef6f06d14e04ddd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4670c62789e9c5555dbfdf514aeb9df24e87c422d85d9dd51e1be7270724890151a316595675dccb59b670484d71ac25f5214f09ff7d677e1b7481a7a71eabce
|
|
7
|
+
data.tar.gz: f7d76c3a04ab77535e9f047dc56b6c05420d2200800c9291fb568f82db99fc752386432c7e2a68a377d4dfa4580ea99ba4da228ce78956f2063c9d403453284f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,263 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented here.
|
|
4
|
+
|
|
1
5
|
## [Unreleased]
|
|
2
6
|
|
|
3
|
-
## [0.
|
|
7
|
+
## [0.2.0] - 2026-07-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`Diff::Result#as_json`** — the canonical diff payload, and now the single
|
|
12
|
+
source of truth for it. `to_json` delegates to it.
|
|
13
|
+
- **`Diff::Result#modifications` and `#stats`** — explicit change counts.
|
|
14
|
+
- **`Rendering::JsonRenderer#to_h`** — the canonical content envelope; `#render`
|
|
15
|
+
is `to_h(...).to_json`.
|
|
16
|
+
- **`DocumentVersion#export(**options)`** — the envelope as a Hash, for embedding
|
|
17
|
+
a version in a larger API response without a JSON round-trip. `as_json` is
|
|
18
|
+
deliberately NOT overridden on the model, so `render json: @version` still
|
|
19
|
+
returns plain ActiveRecord attributes.
|
|
20
|
+
- **`schema_version`** on both envelopes, sourced from
|
|
21
|
+
`Docsmith::JSON_SCHEMA_VERSION`. Clients should branch on this rather than on
|
|
22
|
+
the gem version.
|
|
23
|
+
- **`include_parsed: true`** on the content export, adding a `data` key with the
|
|
24
|
+
parsed document for `content_type: "json"`. No-op for other content types.
|
|
25
|
+
- `Docsmith::InvalidJsonContent`, raised when `include_parsed` is set and the
|
|
26
|
+
content does not parse.
|
|
27
|
+
- `config.html_sanitizer` — controls how `render(:html)` treats stored html
|
|
28
|
+
content. Accepts `nil` (default: escape), any object responding to `#call`, or
|
|
29
|
+
`:unsafe_raw`. See the Security note below.
|
|
30
|
+
- `Docsmith::InvalidHtmlSanitizer` error, raised when `html_sanitizer` is set to
|
|
31
|
+
something that is neither callable, `:unsafe_raw`, nor `nil`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Both JSON exports are a new, single, documented shape.** This is a breaking
|
|
36
|
+
wire-format change with no compatibility flag.
|
|
37
|
+
|
|
38
|
+
**`Diff::Result` — nesting no longer produces a different payload.** `as_json`
|
|
39
|
+
was undefined, so nesting fell through to `Object#as_json` (`instance_values`):
|
|
40
|
+
|
|
41
|
+
```jsonc
|
|
42
|
+
// BEFORE — result.to_json (documented)
|
|
43
|
+
{ "content_type": "markdown", "from_version": 1, "to_version": 3,
|
|
44
|
+
"stats": { "additions": 2, "deletions": 0 },
|
|
45
|
+
"changes": [ { "type": "modification", "position": { "line": 4 }, … } ] }
|
|
46
|
+
|
|
47
|
+
// BEFORE — { diff: result }.to_json, i.e. `render json: { diff: result }`
|
|
48
|
+
// no "stats" at all, and "line" instead of "position"
|
|
49
|
+
{ "diff": { "content_type": "markdown", "from_version": 1, "to_version": 3,
|
|
50
|
+
"changes": [ { "type": "modification", "line": 4, … } ] } }
|
|
51
|
+
|
|
52
|
+
// AFTER — identical either way, plus schema_version and full stats
|
|
53
|
+
{ "schema_version": 1, "content_type": "markdown",
|
|
54
|
+
"from_version": 1, "to_version": 3,
|
|
55
|
+
"stats": { "additions": 2, "deletions": 0, "modifications": 1, "total": 3 },
|
|
56
|
+
"changes": [ { "type": "modification", "position": { "line": 4 }, … } ] }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**`render(:json)` — one envelope instead of two schemas.** It previously returned
|
|
60
|
+
the bare pretty-printed document for `content_type: "json"` and an envelope for
|
|
61
|
+
everything else, so no client could parse it generically:
|
|
62
|
+
|
|
63
|
+
```jsonc
|
|
64
|
+
// BEFORE — content_type "json": no envelope, reformatted
|
|
65
|
+
{ "title": "Doc", "tags": [ "a" ] }
|
|
66
|
+
|
|
67
|
+
// BEFORE — any other content_type: envelope, two keys
|
|
68
|
+
{ "content_type": "markdown", "content": "# Hello" }
|
|
69
|
+
|
|
70
|
+
// AFTER — one shape for every content_type
|
|
71
|
+
{ "schema_version": 1, "document_id": 7, "version_number": 2,
|
|
72
|
+
"content_type": "markdown", "content": "# Hello\n\nSecond draft.",
|
|
73
|
+
"change_summary": "Second draft",
|
|
74
|
+
"author": { "type": "User", "id": 1 },
|
|
75
|
+
"metadata": {}, "created_at": "2026-03-01T14:22:00.000Z" }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Also note: `content` is now **always the exact stored string**, byte for byte.
|
|
79
|
+
JSON documents are no longer pretty-printed on export, so an export matches what
|
|
80
|
+
was versioned. And `USAGE.md` previously documented a `"version"` key that the
|
|
81
|
+
implementation never emitted; the envelope now carries `version_number`.
|
|
82
|
+
|
|
83
|
+
- **`changes` is redesigned: grouped edits with real positions.** The old array had
|
|
84
|
+
no usable semantics. Each *token* was its own entry, so a one-line heading
|
|
85
|
+
rewrite produced seven of them. The `position.line` they carried was a **token
|
|
86
|
+
index, not a line number** — it could exceed the document's line count — and it
|
|
87
|
+
silently mixed coordinate systems, using an old-document index for deletions and
|
|
88
|
+
a new-document index for additions, so the values were not even comparable to
|
|
89
|
+
each other.
|
|
90
|
+
|
|
91
|
+
```jsonc
|
|
92
|
+
// BEFORE — "my document" -> "<h1>this is a new heading</h1>"
|
|
93
|
+
// in a 3-line document. Seven entries; "line" reaches 7.
|
|
94
|
+
"changes": [
|
|
95
|
+
{ "type": "modification", "position": { "line": 1 },
|
|
96
|
+
"old_content": "my", "new_content": "<h1>" },
|
|
97
|
+
{ "type": "modification", "position": { "line": 2 },
|
|
98
|
+
"old_content": "document", "new_content": "this" },
|
|
99
|
+
{ "type": "addition", "position": { "line": 3 }, "content": "is" },
|
|
100
|
+
{ "type": "addition", "position": { "line": 4 }, "content": "a" },
|
|
101
|
+
{ "type": "addition", "position": { "line": 5 }, "content": "new" },
|
|
102
|
+
{ "type": "addition", "position": { "line": 6 }, "content": "heading" },
|
|
103
|
+
{ "type": "addition", "position": { "line": 7 }, "content": "</h1>" }
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
// AFTER — one entry, real offsets and line numbers on both sides
|
|
107
|
+
"changes": [
|
|
108
|
+
{ "type": "replace",
|
|
109
|
+
"old": { "start": 0, "end": 11, "line": 1, "column": 1,
|
|
110
|
+
"text": "my document" },
|
|
111
|
+
"new": { "start": 0, "end": 30, "line": 1, "column": 1,
|
|
112
|
+
"text": "<h1>this is a new heading</h1>" } }
|
|
113
|
+
]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- A contiguous run of edits collapses into **one** entry. A pure-insert run is
|
|
117
|
+
`insert`, pure-delete is `delete`, mixed is `replace`.
|
|
118
|
+
- **Both sides are always present**, so every entry has identical keys. A pure
|
|
119
|
+
insertion carries a zero-width `old` span marking the insertion point, which
|
|
120
|
+
also makes the payload applyable as a patch.
|
|
121
|
+
- `start`/`end` are character offsets (end exclusive); `line`/`column` are real
|
|
122
|
+
1-indexed positions. `content[start...end] == text`, so clients can slice
|
|
123
|
+
their own context.
|
|
124
|
+
- `old` and `new` never share a coordinate space.
|
|
125
|
+
- `Result#changes` returns the same structure symbol-keyed, so Ruby and JSON
|
|
126
|
+
agree on what a change is. `Renderers::Base#render_html` consumes it.
|
|
127
|
+
|
|
128
|
+
- **Edit types and `stats` keys use standard diff vocabulary.** Types are now
|
|
129
|
+
`insert` / `delete` / `replace` (was `addition` / `deletion` / `modification`),
|
|
130
|
+
and `stats` is `insertions` / `deletions` / `replacements` / `total`. Ruby readers
|
|
131
|
+
match: `#insertions`, `#deletions`, `#replacements`. Previously a diff whose every
|
|
132
|
+
line changed reported `additions: 0, deletions: 0` alongside a non-empty
|
|
133
|
+
`changes` array; a replacement is now counted once, and for git-style totals you
|
|
134
|
+
add `replacements` to either side.
|
|
135
|
+
|
|
136
|
+
- The format-aware parsers were reduced to their tokenizer. `Parsers::Markdown` and
|
|
137
|
+
`Parsers::Html` had near-identical copies of `compute`; grouping, offsets, and
|
|
138
|
+
rendering now live once in `Renderers::Base` and subclasses override `tokenize`
|
|
139
|
+
only. A custom renderer registered via `Renderers::Registry` should do the same.
|
|
140
|
+
|
|
141
|
+
- **`Comments::Comment#anchor_data` takes a Hash.** The hand-rolled accessor pair
|
|
142
|
+
that existed only to paper over the `:text` column is gone. Passing a
|
|
143
|
+
pre-serialized JSON String now stores it as a JSON string literal and reads
|
|
144
|
+
back as a String, where the old `:text` column parsed it into a Hash. Callers
|
|
145
|
+
doing `anchor_data: data.to_json` should pass `anchor_data: data`.
|
|
146
|
+
|
|
147
|
+
### Security
|
|
148
|
+
|
|
149
|
+
- **Fixed stored XSS in `version.render(:html)` for `content_type: "html"`.** The
|
|
150
|
+
renderer returned stored content verbatim, so any HTML that originated from a
|
|
151
|
+
user executed in the browser. html content is now escaped by default and
|
|
152
|
+
rendered only through an explicitly configured sanitizer.
|
|
153
|
+
|
|
154
|
+
Docsmith deliberately ships no sanitizer — a safe one requires a real HTML
|
|
155
|
+
parser, and adding Nokogiri would break the zero-system-dependency guarantee.
|
|
156
|
+
|
|
157
|
+
**This is a breaking change for anyone rendering html documents.** To restore
|
|
158
|
+
rendering, pick one:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Recommended — Rails already bundles this via ActionView, so no new gem:
|
|
162
|
+
config.html_sanitizer = ->(html) { Rails::HTML5::SafeListSanitizer.new.sanitize(html) }
|
|
163
|
+
|
|
164
|
+
# Verbatim passthrough, i.e. the old behavior. Only for HTML you fully trust:
|
|
165
|
+
config.html_sanitizer = :unsafe_raw
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
A non-callable, non-`:unsafe_raw` value raises `Docsmith::InvalidHtmlSanitizer`
|
|
169
|
+
instead of degrading to raw output. Markdown and JSON rendering are unaffected,
|
|
170
|
+
and diff output was already escaped for every content type.
|
|
171
|
+
|
|
172
|
+
### Fixed
|
|
173
|
+
|
|
174
|
+
- **`rails generate docsmith:install` never worked.** The generator's action method
|
|
175
|
+
was named `create_migration`, which shadows `Rails::Generators::Migration`'s own
|
|
176
|
+
`create_migration(destination, data, config)` — the method `migration_template`
|
|
177
|
+
calls internally. Running the generator died with `ArgumentError: wrong number of
|
|
178
|
+
arguments (given 3, expected 0)` before writing a single file, so the documented
|
|
179
|
+
first step of installing the gem failed outright in 0.1.0. Renamed to
|
|
180
|
+
`create_migration_file`.
|
|
181
|
+
|
|
182
|
+
This went unnoticed because the generator had no test coverage: `railties`
|
|
183
|
+
provides `rails/generators` and was not a development dependency, so the spec
|
|
184
|
+
suite could not load the generator at all. `railties` is now a **development**
|
|
185
|
+
dependency — deliberately not a runtime one, since docsmith targets plain
|
|
186
|
+
ActiveRecord and nothing in `lib/docsmith.rb` loads Rails. The generator now has
|
|
187
|
+
specs that run it against a temp directory and execute the migration it produces.
|
|
188
|
+
- JSON payload columns (`metadata`, `anchor_data`) are now `:json` rather than
|
|
189
|
+
`:text` in the gem's own schemas. They previously read back as a `Hash` on
|
|
190
|
+
PostgreSQL and as the literal String `"{}"` on SQLite and MySQL, so models saw
|
|
191
|
+
a different type in tests than in production.
|
|
192
|
+
- `rails generate docsmith:install` no longer emits a PostgreSQL-only migration.
|
|
193
|
+
It picks `jsonb` for PostgreSQL-derived adapters and `json` elsewhere, so the
|
|
194
|
+
generated migration runs on MySQL and SQLite instead of failing on `t.jsonb`.
|
|
195
|
+
- `Comments::Anchor.migrate` no longer raises `TypeError` out of the middle of a
|
|
196
|
+
comment migration when an anchor is missing its `anchored_text` or offsets.
|
|
197
|
+
Such anchors are now reported as `orphaned`.
|
|
198
|
+
- `Comments::Comment#anchor_data` no longer raises an unrescued
|
|
199
|
+
`JSON::ParserError` from a plain attribute reader on malformed stored data.
|
|
200
|
+
|
|
201
|
+
### Demo
|
|
202
|
+
|
|
203
|
+
- Added an **HTML & Sanitizer** section (`/pages`) built on a `content_type :html`
|
|
204
|
+
model, showing `render(:html)` output under each `html_sanitizer` mode against
|
|
205
|
+
content containing a `<script>` tag. Seeded automatically.
|
|
206
|
+
- **Fixed stored XSS in the demo's own views.** Sinatra's stock ERB does not
|
|
207
|
+
escape `<%= %>`, so an article title, comment body, or tag name containing a
|
|
208
|
+
`<script>` tag executed in the browser. The route classes now set
|
|
209
|
+
`erb, escape_html: true`, and the two places that intentionally emit HTML
|
|
210
|
+
(`layout.erb`'s `yield`, `diff.erb`'s `to_html`) use `<%== %>`.
|
|
211
|
+
- `demo/db/setup.rb` migrates pre-existing demo databases from the old `:text`
|
|
212
|
+
columns to `:json` via `change_column`, preserving all rows. The `create_table`
|
|
213
|
+
calls are guarded by `table_exists?`, so without this an existing demo DB would
|
|
214
|
+
have silently kept its old column types.
|
|
215
|
+
|
|
216
|
+
### Upgrading
|
|
217
|
+
|
|
218
|
+
- **No migration is required for existing installs.** The column-type fix affects
|
|
219
|
+
the schemas Docsmith ships for its own tests and demo; installs generated
|
|
220
|
+
before this release already have `jsonb` columns, which behave identically.
|
|
221
|
+
The generator change only matters for new installs on MySQL and SQLite, where
|
|
222
|
+
the old `t.jsonb` migration could not run at all.
|
|
223
|
+
- If you render `content_type: "html"` documents, set `config.html_sanitizer`
|
|
224
|
+
before upgrading, or that content will render escaped.
|
|
225
|
+
- If you pass `anchor_data:` a pre-serialized JSON String anywhere, change it to
|
|
226
|
+
pass the Hash directly.
|
|
227
|
+
- **Update any consumer of the two JSON exports.** Both shapes changed and there is
|
|
228
|
+
no compatibility flag. Branch on `schema_version` going forward.
|
|
229
|
+
- If you parsed `render(:json)` for a `content_type: "json"` document, the
|
|
230
|
+
document is no longer the payload root — read `content` (a string) or pass
|
|
231
|
+
`include_parsed: true` and read `data`.
|
|
232
|
+
- If you nested a `Diff::Result` and worked around the missing `stats` by calling
|
|
233
|
+
`JSON.parse(result.to_json)` first, you can now pass the result directly.
|
|
234
|
+
- If you read `additions`/`deletions` as an overall change count, read
|
|
235
|
+
`stats["total"]`, or add `replacements` to either side.
|
|
236
|
+
- Rename `result.additions` to `result.insertions` and `result.modifications` to
|
|
237
|
+
`result.replacements`. `result.deletions` keeps its name.
|
|
238
|
+
- If you walked `result.changes`, it is now grouped edits with `old`/`new` spans
|
|
239
|
+
rather than a flat token list. Anything reading `c[:line]` or `c[:content]`
|
|
240
|
+
needs updating — and note that `c[:line]` was never a line number.
|
|
241
|
+
- If you subclassed a parser or registered a custom diff renderer, override
|
|
242
|
+
`#tokenize` (returning `[text, start_offset]` pairs) instead of `#compute`.
|
|
243
|
+
- **`config.diff_context_lines` is removed.** It was declared and documented but
|
|
244
|
+
read by nothing, so it never had any effect. With real `start`/`end` offsets on
|
|
245
|
+
both sides of every edit, a client can slice whatever context it needs from the
|
|
246
|
+
document text.
|
|
247
|
+
|
|
248
|
+
## [0.1.0] - 2026-04-12
|
|
249
|
+
|
|
250
|
+
First public release.
|
|
251
|
+
|
|
252
|
+
### Added
|
|
4
253
|
|
|
5
|
-
-
|
|
254
|
+
- `Docsmith::Versionable` mixin — include in any ActiveRecord model to enable versioning
|
|
255
|
+
- Snapshot-based versioning: full content snapshots for HTML, Markdown, and JSON with instant rollback to any version
|
|
256
|
+
- Format-aware diff engine: word-level diffs for Markdown, tag-atomic diffs for HTML
|
|
257
|
+
- Inline and document-level comments with threading, resolution, and version migration
|
|
258
|
+
- Debounced auto-save with configurable threshold per class
|
|
259
|
+
- Per-class and global configuration via `docsmith_config` block
|
|
260
|
+
- Lifecycle events: `version_created`, `version_restored`, `version_tagged`
|
|
261
|
+
- Standalone `Docsmith::Document` service API — works without a model mixin
|
|
262
|
+
- `rails generate docsmith:install` generator with migration
|
|
263
|
+
- Zero system dependencies — pure Ruby on top of ActiveRecord and diff-lcs
|
data/CODE_OF_CONDUCT.md
CHANGED
|
@@ -1,132 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Code of Conduct
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is a small open source project, maintained by one person. If you contribute, a few things I ask:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
-
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
-
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
-
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
-
identity and orientation.
|
|
5
|
+
**Be straight.** If something is broken, say so. If you disagree with a design decision, explain why. Honest feedback is welcome — rudeness isn't.
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
diverse, inclusive, and healthy community.
|
|
7
|
+
**Be patient.** I review issues and PRs in my own time. If something takes a while, a polite nudge is fine.
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
**Be constructive.** Bug reports with reproducible examples, PRs with context, questions that show you tried first — these make the project better for everyone.
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
community include:
|
|
19
|
-
|
|
20
|
-
* Demonstrating empathy and kindness toward other people
|
|
21
|
-
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
-
* Giving and gracefully accepting constructive feedback
|
|
23
|
-
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
-
and learning from the experience
|
|
25
|
-
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
-
community
|
|
27
|
-
|
|
28
|
-
Examples of unacceptable behavior include:
|
|
29
|
-
|
|
30
|
-
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
-
any kind
|
|
32
|
-
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
-
* Public or private harassment
|
|
34
|
-
* Publishing others' private information, such as a physical or email address,
|
|
35
|
-
without their explicit permission
|
|
36
|
-
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
-
professional setting
|
|
38
|
-
|
|
39
|
-
## Enforcement Responsibilities
|
|
40
|
-
|
|
41
|
-
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
-
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
-
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
-
or harmful.
|
|
45
|
-
|
|
46
|
-
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
-
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
-
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
-
decisions when appropriate.
|
|
50
|
-
|
|
51
|
-
## Scope
|
|
52
|
-
|
|
53
|
-
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
-
an individual is officially representing the community in public spaces.
|
|
55
|
-
Examples of representing our community include using an official email address,
|
|
56
|
-
posting via an official social media account, or acting as an appointed
|
|
57
|
-
representative at an online or offline event.
|
|
58
|
-
|
|
59
|
-
## Enforcement
|
|
60
|
-
|
|
61
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
-
reported to the community leaders responsible for enforcement at
|
|
63
|
-
[INSERT CONTACT METHOD].
|
|
64
|
-
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
-
|
|
66
|
-
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
-
reporter of any incident.
|
|
68
|
-
|
|
69
|
-
## Enforcement Guidelines
|
|
70
|
-
|
|
71
|
-
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
-
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
-
|
|
74
|
-
### 1. Correction
|
|
75
|
-
|
|
76
|
-
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
-
unprofessional or unwelcome in the community.
|
|
78
|
-
|
|
79
|
-
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
-
clarity around the nature of the violation and an explanation of why the
|
|
81
|
-
behavior was inappropriate. A public apology may be requested.
|
|
82
|
-
|
|
83
|
-
### 2. Warning
|
|
84
|
-
|
|
85
|
-
**Community Impact**: A violation through a single incident or series of
|
|
86
|
-
actions.
|
|
87
|
-
|
|
88
|
-
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
-
interaction with the people involved, including unsolicited interaction with
|
|
90
|
-
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
-
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
-
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
-
ban.
|
|
94
|
-
|
|
95
|
-
### 3. Temporary Ban
|
|
96
|
-
|
|
97
|
-
**Community Impact**: A serious violation of community standards, including
|
|
98
|
-
sustained inappropriate behavior.
|
|
99
|
-
|
|
100
|
-
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
-
communication with the community for a specified period of time. No public or
|
|
102
|
-
private interaction with the people involved, including unsolicited interaction
|
|
103
|
-
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
-
Violating these terms may lead to a permanent ban.
|
|
105
|
-
|
|
106
|
-
### 4. Permanent Ban
|
|
107
|
-
|
|
108
|
-
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
-
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
-
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
-
|
|
112
|
-
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
-
community.
|
|
114
|
-
|
|
115
|
-
## Attribution
|
|
116
|
-
|
|
117
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
-
version 2.1, available at
|
|
119
|
-
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
-
|
|
121
|
-
Community Impact Guidelines were inspired by
|
|
122
|
-
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
-
|
|
124
|
-
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
-
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
-
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
-
|
|
128
|
-
[homepage]: https://www.contributor-covenant.org
|
|
129
|
-
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
-
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
-
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
-
[translations]: https://www.contributor-covenant.org/translations
|
|
11
|
+
That's it. No enforcement ladder, no formal committee. If something feels off, reach out at swastik.thapaliya@gmail.com and we'll sort it out like adults.
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Docsmith
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/docsmith)
|
|
4
|
+
|
|
3
5
|
Docsmith adds snapshot-based versioning, format-aware diffs, and inline comments to any
|
|
4
6
|
ActiveRecord model — with zero system dependencies.
|
|
5
7
|
|
|
@@ -35,10 +37,36 @@ article.save!
|
|
|
35
37
|
article.save_version!(author: current_user, summary: "First draft")
|
|
36
38
|
|
|
37
39
|
result = article.diff_from(1)
|
|
38
|
-
result.
|
|
40
|
+
result.stats # => {"insertions"=>1, "deletions"=>0, "replacements"=>1, "total"=>2}
|
|
41
|
+
result.changes # grouped edits with character offsets and line numbers
|
|
39
42
|
result.to_html # <ins>/<del> markup
|
|
43
|
+
result.as_json # canonical JSON payload, identical when nested
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Example App
|
|
47
|
+
|
|
48
|
+
A self-contained Sinatra demo is in [`demo/`](demo/). It shows versioning, diffs, and comments working end-to-end — no Rails required.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd demo
|
|
52
|
+
bundle install
|
|
53
|
+
bundle exec rackup
|
|
54
|
+
# open http://localhost:9292
|
|
40
55
|
```
|
|
41
56
|
|
|
57
|
+
The demo loads the gem from the parent directory as a path dependency, so the
|
|
58
|
+
`bundle exec` prefix is required. `bundle exec ruby app.rb` also works and
|
|
59
|
+
serves on port 4567.
|
|
60
|
+
|
|
61
|
+
Two sections: **Articles** covers markdown versioning, diffs, tags, and comments.
|
|
62
|
+
**HTML & Sanitizer** uses `content_type :html` to show what `render(:html)` returns
|
|
63
|
+
under each `config.html_sanitizer` mode, side by side, against content containing a
|
|
64
|
+
`<script>` tag.
|
|
65
|
+
|
|
66
|
+
The demo installs `rails-html-sanitizer` to demonstrate the recommended sanitizer
|
|
67
|
+
setup, and `erubi` to turn on ERB escaping. Neither is a Docsmith dependency — the
|
|
68
|
+
gem itself still has none beyond ActiveRecord, ActiveSupport, and diff-lcs.
|
|
69
|
+
|
|
42
70
|
## Documentation
|
|
43
71
|
|
|
44
72
|
See **[USAGE.md](USAGE.md)** for full documentation including:
|
|
@@ -61,6 +89,12 @@ bundle exec rspec # run tests
|
|
|
61
89
|
bin/console # interactive console
|
|
62
90
|
```
|
|
63
91
|
|
|
92
|
+
## How this was built
|
|
93
|
+
|
|
94
|
+
Honest note: this gem was built over two weekends using [Claude Code](https://claude.ai/code) with the superpowers plugin. Not vibe-coding — the planning, architecture decisions, and implementation were all deliberate. If you're curious how it came together, the planning docs and implementation notes are in [docs/superpowers](docs/superpowers).
|
|
95
|
+
|
|
96
|
+
Early designs got ambitious fast — branching, merging, conflict resolution. Turns out that's a lot of machinery for what is ultimately a document version manager. I stripped it back to what actually matters: snapshots, diffs, and comments. You can see all the planning in [docs/superpowers](docs/superpowers).
|
|
97
|
+
|
|
64
98
|
## License
|
|
65
99
|
|
|
66
100
|
MIT — see [LICENSE.txt](LICENSE.txt).
|