activeadmin_annotations 0.1.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 +7 -0
- data/CHANGELOG.md +14 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/CONTRIBUTING.md +29 -0
- data/GOVERNANCE.md +29 -0
- data/LICENSE.md +21 -0
- data/README.md +321 -0
- data/SECURITY.md +27 -0
- data/activeadmin_annotations.gemspec +68 -0
- data/admin/annotation_reviews.rb +102 -0
- data/admin/annotations.rb +146 -0
- data/app/assets/controllers/activeadmin_annotations/annotator_controller.js +483 -0
- data/app/assets/controllers/activeadmin_annotations/clipboard_controller.js +73 -0
- data/app/assets/stylesheets/activeadmin_annotations.css +362 -0
- data/app/helpers/active_admin/annotations/panel_helper.rb +95 -0
- data/app/models/active_admin/annotations/annotation.rb +37 -0
- data/app/models/active_admin/annotations/application_record.rb +13 -0
- data/app/models/active_admin/annotations/review.rb +54 -0
- data/app/services/active_admin/annotations/review_service.rb +133 -0
- data/app/views/active_admin/annotations/_copy_details_action.html.erb +21 -0
- data/app/views/active_admin/annotations/_panel.html.erb +98 -0
- data/app/views/active_admin/annotations/_read_only_content.html.erb +3 -0
- data/config/polyrun_coverage.yml +8 -0
- data/db/migrate/20260617120000_create_activeadmin_annotations_tables.rb +41 -0
- data/db/migrate/20260706150000_add_metadata_json_gin_index_to_annotation_reviews.rb +9 -0
- data/db/migrate/20260710120000_add_panel_query_index_to_annotation_spans.rb +10 -0
- data/lib/activeadmin/annotations/annotatable.rb +15 -0
- data/lib/activeadmin/annotations/categories.rb +13 -0
- data/lib/activeadmin/annotations/content_revision.rb +101 -0
- data/lib/activeadmin/annotations/context.rb +11 -0
- data/lib/activeadmin/annotations/copy_text.rb +98 -0
- data/lib/activeadmin/annotations/engine.rb +49 -0
- data/lib/activeadmin/annotations/exporter.rb +99 -0
- data/lib/activeadmin/annotations/review_access.rb +29 -0
- data/lib/activeadmin/annotations/review_panel.rb +145 -0
- data/lib/activeadmin/annotations/version.rb +7 -0
- data/lib/activeadmin/annotations.rb +37 -0
- data/lib/activeadmin_annotations.rb +13 -0
- data/sig/activeadmin/annotations.rbs +5 -0
- metadata +393 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 27efb5276816339c66980cb3e77752ab208f0a3f25ac290b051957f48542b489
|
|
4
|
+
data.tar.gz: 8367a3f0d88f6ad69e2c18721f1576a0ed8e2027ede89a20d64edfa252b238a6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: aaf877c12d55510f4713004c8716fa87e0ab16c32cdd459e2ea3a5e070faeb7ecb4fc7a7969c1b40a499499ebba5c091c3944ca1d1f0ccbfe21ebc9d969531d9
|
|
7
|
+
data.tar.gz: 13991c461e4965f4e86800ea9585ae548ab36a69b64ea9878d86288aeb63a3710c5b0371b72b3bfc3451c59defc24719e941b49a8f84f90650af336820b58c1e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-07-10)
|
|
4
|
+
|
|
5
|
+
- Add span-level annotations inside a bounded ActiveAdmin block.
|
|
6
|
+
- Add review and span models with JSONL export from the reviews index.
|
|
7
|
+
- Add Stimulus annotator and clipboard controllers with panel helper `activeadmin_annotations_panel`.
|
|
8
|
+
- Add `ActiveAdmin::Annotations::ReviewPanel` for revision pinning, stale banners, and advance-to-latest review flows.
|
|
9
|
+
- Add optional content revision pinning (`:auto`, `:active_version`, custom resolvers).
|
|
10
|
+
- Add `ActiveAdmin::Annotations::Annotatable` concern for host models.
|
|
11
|
+
- Add configurable review admin menu visibility via `review_menu_visible`.
|
|
12
|
+
- Add optional annotation categories and copy instructions via initializer settings.
|
|
13
|
+
- Add composite index on `annotation_spans` for panel load by review, field, and revision.
|
|
14
|
+
- Add PostgreSQL GIN index on review `metadata_json` for follow-up filtering.
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We pledge to make participation in our community a harassment-free experience for everyone. We operate on principles of mutual respect, privacy, and authentic engagement. We value substantive contributions and clarity on intentions.
|
|
6
|
+
|
|
7
|
+
## Our standards
|
|
8
|
+
|
|
9
|
+
Examples of behavior that contributes to a positive environment include:
|
|
10
|
+
|
|
11
|
+
- Authenticity: engaging with genuine curiosity and admitting uncertainty rather than feigning knowledge.
|
|
12
|
+
- Responsible innovation: taking full responsibility for any content or code contributed, whether manually written or generated by automation tools.
|
|
13
|
+
- Gentle correction: responding politely to errors. We view mistakes as opportunities for learning, provided they are addressed with humility.
|
|
14
|
+
- Inclusive language: using language that welcomes diverse perspectives and respects the privacy and identity of all participants.
|
|
15
|
+
|
|
16
|
+
Examples of unacceptable behavior include:
|
|
17
|
+
|
|
18
|
+
- Harassment: public or private harassment, trolling, or insulting comments.
|
|
19
|
+
- Weaponized complexity: using jargon or overwhelming volume (including automated spam) to silence others.
|
|
20
|
+
- Publishing private information: sharing others' data or personal context without explicit permission.
|
|
21
|
+
|
|
22
|
+
## Artificial intelligence and automation
|
|
23
|
+
|
|
24
|
+
In accordance with our commitment to collective awareness:
|
|
25
|
+
|
|
26
|
+
- Contributors are responsible for the accuracy and security of any AI-generated artifacts they submit.
|
|
27
|
+
- "The AI wrote it" is not a valid excuse for introducing bugs, security vulnerabilities, or bias.
|
|
28
|
+
|
|
29
|
+
## Enforcement
|
|
30
|
+
|
|
31
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@kiskolabs.com. All complaints will be reviewed and investigated promptly and fairly.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Contributing Guidelines
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to activeadmin_annotations. We value learning over perfection but require rigor and responsibility.
|
|
4
|
+
|
|
5
|
+
## The golden rule of automation
|
|
6
|
+
|
|
7
|
+
We welcome the use of AI and automation tools to reduce toil, but you must strictly adhere to the following:
|
|
8
|
+
|
|
9
|
+
1. You are the author. You act as the responsible agent for any code you submit. You must review, debug, and understand every line.
|
|
10
|
+
2. Manage cognitive load. Do not submit massive, unreviewed automated dumps. Respect the reviewers' time by annotating complex logic.
|
|
11
|
+
3. Security. Never feed project secrets or private context into public AI models.
|
|
12
|
+
|
|
13
|
+
## How to contribute
|
|
14
|
+
|
|
15
|
+
### Reporting issues
|
|
16
|
+
|
|
17
|
+
- Verify accuracy before posting. Avoid generalizations.
|
|
18
|
+
- Use issue templates to provide clear goals, constraints, and reproduction steps.
|
|
19
|
+
|
|
20
|
+
### Pull request process
|
|
21
|
+
|
|
22
|
+
- Scope: keep PRs focused on a single goal.
|
|
23
|
+
- Context: explain why the change is necessary.
|
|
24
|
+
- Testing: run `make test` locally before opening a PR.
|
|
25
|
+
|
|
26
|
+
### Review process
|
|
27
|
+
|
|
28
|
+
- We encourage productive friction. Expect questions about your approach.
|
|
29
|
+
- If a reviewer suggests a change, view it as mutual aid, not criticism.
|
data/GOVERNANCE.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Project Governance
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This project balances individual autonomy with collective coordination. We aim for a workflow that supports rapid iteration while maintaining strict guardrails for safety and architectural integrity.
|
|
6
|
+
|
|
7
|
+
## Roles
|
|
8
|
+
|
|
9
|
+
### Contributors
|
|
10
|
+
|
|
11
|
+
Anyone who submits code, documentation, or participates in discussions. Contributors are expected to exercise direct action—taking ownership of problems they identify—while adhering to our quality standards.
|
|
12
|
+
|
|
13
|
+
### Maintainers (Human Oversight)
|
|
14
|
+
|
|
15
|
+
Maintainers are responsible for:
|
|
16
|
+
|
|
17
|
+
1. Strategic judgment: defining scope and architectural direction.
|
|
18
|
+
2. Review: verifying that contributions (human or automated) meet security and logic standards.
|
|
19
|
+
3. Consensus building: facilitating decisions when the community is divided.
|
|
20
|
+
|
|
21
|
+
## Decision making process
|
|
22
|
+
|
|
23
|
+
### Lazy consensus
|
|
24
|
+
|
|
25
|
+
For most routine changes, we operate on lazy consensus. If a proposal is made and no objections are raised within two weeks, it is considered approved.
|
|
26
|
+
|
|
27
|
+
### Strategic alignment
|
|
28
|
+
|
|
29
|
+
Major architectural changes, high-risk automation integrations, or changes that affect business logic require explicit approval from the maintainers. We prioritize substance over performance—a change must solve a real problem, not just appear polished.
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 amkisko
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# activeadmin_annotations
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/activeadmin_annotations) [](https://github.com/amkisko/activeadmin_annotations.rb/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
Collect span-level text highlights and comments inside a bounded ActiveAdmin block.
|
|
6
|
+
|
|
7
|
+
The gem stays domain-agnostic: the host app chooses what review context means, which optional categories to offer, where to render the panel, and whether annotations are pinned to content revisions.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Ruby 3.2+
|
|
12
|
+
- Rails 7.1+
|
|
13
|
+
- ActiveAdmin 3.0+
|
|
14
|
+
- PostgreSQL recommended for production (SQLite works for development; some metadata filters use Postgres JSON operators)
|
|
15
|
+
|
|
16
|
+
## Host app setup
|
|
17
|
+
|
|
18
|
+
Subject models and the reviewer model should use UUID primary keys. Migrations store `subject_id` and `reviewer_id` as UUIDs.
|
|
19
|
+
|
|
20
|
+
Set the reviewer model in the initializer:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
ActiveAdmin::Annotations.reviewer_class_name = "User"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The reviewer must be available as `current_user` (or your ActiveAdmin authentication helper) when the panel renders.
|
|
27
|
+
|
|
28
|
+
The Review admin menu item is visible when `review_menu_visible` returns true for the signed-in user. Default checks `user.administrator?`. Override in the initializer:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
ActiveAdmin::Annotations.review_menu_visible = ->(user) { user&.can_review_annotations? }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Add to your Gemfile:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
gem "activeadmin_annotations"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bundle install
|
|
46
|
+
rails activeadmin_annotations:install:migrations
|
|
47
|
+
rails db:migrate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Configure the reviewer model and optional categories:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# config/initializers/activeadmin_annotations.rb
|
|
54
|
+
ActiveAdmin::Annotations.reviewer_class_name = "User"
|
|
55
|
+
ActiveAdmin::Annotations.review_menu_visible = ->(user) { user&.administrator? }
|
|
56
|
+
ActiveAdmin::Annotations.category_label = "Category"
|
|
57
|
+
ActiveAdmin::Annotations.categories = {
|
|
58
|
+
"needs_follow_up" => "Needs follow-up"
|
|
59
|
+
}
|
|
60
|
+
ActiveAdmin::Annotations.copy_instructions = <<~TEXT
|
|
61
|
+
These annotations capture span-level review notes for evaluation datasets.
|
|
62
|
+
TEXT
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`copy_instructions` can be a string or a callable `(annotation, review) -> string`.
|
|
66
|
+
It is prepended to the text copied from the annotation show page.
|
|
67
|
+
|
|
68
|
+
## Host model
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class Article < ApplicationRecord
|
|
72
|
+
include ActiveAdmin::Annotations::Annotatable
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## ActiveAdmin panel (basic, no content revisions)
|
|
77
|
+
|
|
78
|
+
When the subject does not use content revisions, render the panel directly:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
text_node helpers.activeadmin_annotations_panel(
|
|
82
|
+
subject: resource,
|
|
83
|
+
field: :body,
|
|
84
|
+
context: {title: resource.title, author: resource.author_name},
|
|
85
|
+
context_panels: [
|
|
86
|
+
{title: "Metadata", content: JSON.pretty_generate(resource.metadata)}
|
|
87
|
+
],
|
|
88
|
+
content: markdown(resource.body)
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The gem stores annotations against the live subject text. If the reviewed text changes, stale detection uses `context_digest` only. No version UI is shown.
|
|
93
|
+
|
|
94
|
+
## Content revisions (optional)
|
|
95
|
+
|
|
96
|
+
Annotations use character offsets. If reviewed text can change in place, pin annotations to a content revision.
|
|
97
|
+
|
|
98
|
+
The gem does not depend on `active_version`. Revisions are optional and disabled unless:
|
|
99
|
+
|
|
100
|
+
- `content_revision_strategy` is `:active_version` or `:auto` (default), and the subject model uses `has_revisions` from [active_version](https://github.com/amkisko/active_version.rb), or
|
|
101
|
+
- you provide custom revision resolvers (below).
|
|
102
|
+
|
|
103
|
+
### Using active_version (recommended when content changes)
|
|
104
|
+
|
|
105
|
+
`active_version` is not a gem dependency. When present on a subject, the gem follows this convention:
|
|
106
|
+
|
|
107
|
+
- `subject.class.has_revisions?` is true
|
|
108
|
+
- `subject.current_version` returns the head revision number
|
|
109
|
+
- `subject.at_version(number)` returns a read-only copy at that revision
|
|
110
|
+
|
|
111
|
+
Pinned version `N` on the head `H` resolves content as:
|
|
112
|
+
|
|
113
|
+
- `N == H` → live `subject`
|
|
114
|
+
- `N < H` → `subject.at_version(N + 1)` (active_version stores the previous text in the next revision row)
|
|
115
|
+
|
|
116
|
+
Setup in the host app:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
rails g active_version:revisions Article
|
|
120
|
+
rails db:migrate
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
class Article < ApplicationRecord
|
|
125
|
+
include ActiveAdmin::Annotations::Annotatable
|
|
126
|
+
has_revisions(only: %i[title body])
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then either render the panel through `ActiveAdmin::Annotations::ReviewPanel` (handles pinning, stale state, and version filtering):
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
text_node ActiveAdmin::Annotations::ReviewPanel.render(
|
|
134
|
+
subject: resource,
|
|
135
|
+
reviewer: current_user,
|
|
136
|
+
field: :body,
|
|
137
|
+
view_context: self,
|
|
138
|
+
context_builder: ->(content_subject) { {title: content_subject.title} },
|
|
139
|
+
content_builder: ->(content_subject) { markdown(content_subject.body) },
|
|
140
|
+
advance_review_url: advance_annotation_review_admin_article_path(resource)
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Wire the stale-banner action on the host subject resource:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# app/admin/articles.rb
|
|
148
|
+
ActiveAdmin.register Article do
|
|
149
|
+
member_action :advance_annotation_review, method: :post do
|
|
150
|
+
context = {title: resource.title, body: resource.body}
|
|
151
|
+
ActiveAdmin::Annotations::ReviewService.advance_to_latest!(
|
|
152
|
+
subject: resource,
|
|
153
|
+
reviewer: current_user,
|
|
154
|
+
context: context,
|
|
155
|
+
latest_content_revision_version: resource.current_version
|
|
156
|
+
)
|
|
157
|
+
redirect_to resource_path, notice: "Review moved to the latest content version."
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Pass the same path helper to `ReviewPanel.render` as `advance_review_url`. The panel shows the button only when the review is stale and a URL is present.
|
|
163
|
+
|
|
164
|
+
Or pass revision options manually to `activeadmin_annotations_panel`.
|
|
165
|
+
|
|
166
|
+
Enable active_version detection explicitly:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
ActiveAdmin::Annotations.content_revision_strategy = :active_version
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Custom revision backend (no active_version)
|
|
173
|
+
|
|
174
|
+
Disable auto-detection and provide callables (use `:auto`, not `:none`):
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
ActiveAdmin::Annotations.content_revision_strategy = :auto
|
|
178
|
+
|
|
179
|
+
ActiveAdmin::Annotations.current_content_revision_version = lambda do |subject|
|
|
180
|
+
subject.published_version
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
ActiveAdmin::Annotations.content_subject_for_revision = lambda do |subject, version|
|
|
184
|
+
subject.versions.find_by!(number: version)
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
With custom resolvers, revision mode is enabled even without `active_version`.
|
|
189
|
+
|
|
190
|
+
### Revision configuration reference
|
|
191
|
+
|
|
192
|
+
- `content_revision_strategy` — `:auto` (default), `:active_version`, or `:none` / `false`
|
|
193
|
+
- `current_content_revision_version` — `(subject) -> Integer` override for head version
|
|
194
|
+
- `content_subject_for_revision` — `(subject, version) -> Object` override for rendered content
|
|
195
|
+
- `content_revision_label` — `false`, format string (`"Draft %{version}"`), or `(version) -> String`
|
|
196
|
+
- `stale_content_review_message` — string (`%{pinned_version}`, `%{latest_version}`) or `(review, pinned_version:, latest_version:)`
|
|
197
|
+
- `stale_context_message` — string or `(review)` when revisions are off but `context_digest` changed
|
|
198
|
+
|
|
199
|
+
Disable all revision UI and logic:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
ActiveAdmin::Annotations.content_revision_strategy = :none
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Storage
|
|
206
|
+
|
|
207
|
+
- `ActiveAdmin::Annotations::Review` — one row per subject + reviewer, with frozen `context_json`
|
|
208
|
+
- `ActiveAdmin::Annotations::Annotation` — span highlights tied to a review and field name
|
|
209
|
+
- `content_revision_version` — optional pin when revision mode is enabled (defaults to `0`)
|
|
210
|
+
|
|
211
|
+
## Export
|
|
212
|
+
|
|
213
|
+
ActiveAdmin registers review and span resources. Use the Export JSONL action on the reviews index for downstream tooling.
|
|
214
|
+
|
|
215
|
+
## Theme customization
|
|
216
|
+
|
|
217
|
+
The panel loads `activeadmin_annotations.css` and uses stable `aa-annotations-*` class names. Action buttons use ActiveAdmin's `action-item-button` so they inherit your admin chrome; colors, spacing, borders, and text highlights are overridden in the host app.
|
|
218
|
+
|
|
219
|
+
### Override styles
|
|
220
|
+
|
|
221
|
+
Add a host stylesheet that loads after the gem asset (for example in your ActiveAdmin layout or `app/assets/stylesheets/active_admin/`):
|
|
222
|
+
|
|
223
|
+
```css
|
|
224
|
+
/* app/assets/stylesheets/active_admin/custom_annotations.css */
|
|
225
|
+
.aa-annotations-sidebar {
|
|
226
|
+
border-color: var(--border-color, #e5e7eb);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.aa-annotations-save-button {
|
|
230
|
+
background-color: var(--primary, #2563eb);
|
|
231
|
+
border-color: var(--primary, #2563eb);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
::highlight(aa-annotations-saved) {
|
|
235
|
+
background-color: color-mix(in srgb, var(--primary, #2563eb) 30%, #fef08a);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
::highlight(aa-annotations-pending) {
|
|
239
|
+
background-color: color-mix(in srgb, var(--primary, #2563eb) 40%, #fef08a);
|
|
240
|
+
text-decoration-color: var(--primary, #2563eb);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Gem defaults include `.dark .aa-annotations-*` rules for ActiveAdmin 4 dark mode. Mirror host overrides under `.dark` when you change light-mode colors.
|
|
245
|
+
|
|
246
|
+
Main layout hooks: `.aa-annotations-panel`, `.aa-annotations-layout`, `.aa-annotations-sidebar`, `.aa-annotations-annotatable`, `.aa-annotations-composer`, `.aa-annotations-stale-banner`.
|
|
247
|
+
|
|
248
|
+
### Override partials
|
|
249
|
+
|
|
250
|
+
Copy views from the gem into `app/views/active_admin/annotations/`:
|
|
251
|
+
|
|
252
|
+
- `_panel.html.erb` — review UI and composer
|
|
253
|
+
- `_read_only_content.html.erb` — content-only fallback when no reviewer is signed in
|
|
254
|
+
- `_copy_details_action.html.erb` — copy action on the span show page
|
|
255
|
+
|
|
256
|
+
Override `_panel.html.erb` when you need different markup or to pin a custom Stimulus controller on the annotator root.
|
|
257
|
+
|
|
258
|
+
## Authorization
|
|
259
|
+
|
|
260
|
+
The gem registers ActiveAdmin resources for reviews and spans. Member actions use your host authorization adapter.
|
|
261
|
+
|
|
262
|
+
Span create, update, destroy, and copy actions verify that the signed-in reviewer owns the target review. Host policies should still gate who may access annotation resources at all (`create?`, `update?`, `destroy?`, `show?`).
|
|
263
|
+
|
|
264
|
+
With [ActionPolicy](https://github.com/palkan/action_policy) (or ActiveAdmin's ActionPolicy adapter), allow the span Copy details action explicitly:
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
# app/policies/active_admin/annotations/annotation_policy.rb
|
|
268
|
+
class ActiveAdmin::Annotations::AnnotationPolicy < ApplicationPolicy
|
|
269
|
+
def copy_text?
|
|
270
|
+
show?
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Review resources use the usual CRUD predicates (`index?`, `show?`, `create?`, `update?`) from your app policy base class.
|
|
276
|
+
|
|
277
|
+
Place policies under `app/policies/active_admin/annotations/` so Zeitwerk maps them to `ActiveAdmin::Annotations::*Policy`.
|
|
278
|
+
|
|
279
|
+
## Dependencies
|
|
280
|
+
|
|
281
|
+
Required:
|
|
282
|
+
|
|
283
|
+
- Rails >= 7.1
|
|
284
|
+
- ActiveAdmin >= 3.0
|
|
285
|
+
|
|
286
|
+
Optional (host app):
|
|
287
|
+
|
|
288
|
+
- `active_version` for revision snapshots — not bundled; follow active_version setup when content can regenerate
|
|
289
|
+
|
|
290
|
+
## Development
|
|
291
|
+
|
|
292
|
+
From the gem root:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
bundle install
|
|
296
|
+
bundle exec appraisal install
|
|
297
|
+
bundle exec rubocop
|
|
298
|
+
bundle exec polyrun parallel-rspec --workers 5 --merge-failures
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Matrixed Rails versions use [Appraisal](https://github.com/thoughtbot/appraisal): `gemfiles/rails72.gemfile`, `rails8ruby34.gemfile`, and `rails8truffleruby.gemfile`. Run `bundle exec appraisal rspec` to execute RSpec in each gemfile context.
|
|
302
|
+
|
|
303
|
+
Shared agent guidance is managed with [pray](https://github.com/kiskolabs/pray) via `Prayfile`.
|
|
304
|
+
|
|
305
|
+
[Trunk](https://docs.trunk.io) config lives in `.trunk/`; CI can run `trunk` via `.github/workflows/_trunk_check.yml`. Releases: `make release` or `usr/bin/release.rb`.
|
|
306
|
+
|
|
307
|
+
## Contributing
|
|
308
|
+
|
|
309
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and pull requests are welcome at https://github.com/amkisko/activeadmin_annotations.rb/issues
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
MIT — see [LICENSE.md](LICENSE.md).
|
|
314
|
+
|
|
315
|
+
## Sponsors
|
|
316
|
+
|
|
317
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
318
|
+
|
|
319
|
+
<a href="https://www.kiskolabs.com">
|
|
320
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
321
|
+
</a>
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SECURITY
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Do NOT open a public GitHub issue for security vulnerabilities.
|
|
6
|
+
|
|
7
|
+
Email security details to: security@kiskolabs.com
|
|
8
|
+
|
|
9
|
+
Include: description, steps to reproduce, potential impact, and suggested fix (if available).
|
|
10
|
+
|
|
11
|
+
### Response timeline
|
|
12
|
+
|
|
13
|
+
- We will acknowledge receipt of your report
|
|
14
|
+
- We will provide an initial assessment
|
|
15
|
+
- We will keep you informed of our progress and resolution timeline
|
|
16
|
+
|
|
17
|
+
### Disclosure policy
|
|
18
|
+
|
|
19
|
+
- We will work with you to understand and resolve the issue
|
|
20
|
+
- We will credit you for the discovery (unless you prefer to remain anonymous)
|
|
21
|
+
- We will publish a security advisory after the vulnerability is patched
|
|
22
|
+
- We will coordinate public disclosure with you
|
|
23
|
+
|
|
24
|
+
## Automation security
|
|
25
|
+
|
|
26
|
+
- Context isolation: it is strictly forbidden to include production credentials, API keys, or personally identifiable information in prompts sent to third-party LLMs or automation services.
|
|
27
|
+
- Supply chain: all automated dependencies must be verified.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/activeadmin/annotations/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "activeadmin_annotations"
|
|
7
|
+
spec.version = ActiveAdmin::Annotations::VERSION
|
|
8
|
+
spec.authors = ["Andrei Makarov"]
|
|
9
|
+
spec.email = ["contact@kiskolabs.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Span-level annotations for ActiveAdmin"
|
|
12
|
+
spec.description = "Collect text highlights and comments inside a bounded ActiveAdmin block, with optional review context, content revision pinning, and JSONL export."
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.platform = Gem::Platform::RUBY
|
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
16
|
+
|
|
17
|
+
repository_url = "https://github.com/amkisko/activeadmin_annotations.rb"
|
|
18
|
+
|
|
19
|
+
spec.homepage = repository_url
|
|
20
|
+
spec.metadata = {
|
|
21
|
+
"homepage_uri" => repository_url,
|
|
22
|
+
"source_code_uri" => "#{repository_url}/tree/main",
|
|
23
|
+
"changelog_uri" => "#{repository_url}/blob/main/CHANGELOG.md",
|
|
24
|
+
"bug_tracker_uri" => "#{repository_url}/issues",
|
|
25
|
+
"documentation_uri" => "#{repository_url}#readme",
|
|
26
|
+
"rubygems_mfa_required" => "true"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
spec.files = Dir.chdir(__dir__) do
|
|
30
|
+
%w[
|
|
31
|
+
CHANGELOG.md
|
|
32
|
+
CODE_OF_CONDUCT.md
|
|
33
|
+
CONTRIBUTING.md
|
|
34
|
+
GOVERNANCE.md
|
|
35
|
+
LICENSE.md
|
|
36
|
+
README.md
|
|
37
|
+
SECURITY.md
|
|
38
|
+
activeadmin_annotations.gemspec
|
|
39
|
+
].select { |path| File.file?(path) } +
|
|
40
|
+
Dir["{app,admin,config,db,lib,sig}/**/*"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
spec.require_paths = ["lib"]
|
|
44
|
+
|
|
45
|
+
spec.add_dependency "rails", ">= 7.1"
|
|
46
|
+
spec.add_dependency "activeadmin", ">= 3.0"
|
|
47
|
+
|
|
48
|
+
spec.add_development_dependency "appraisal", "~> 2"
|
|
49
|
+
spec.add_development_dependency "bundler-audit", "~> 0.9"
|
|
50
|
+
spec.add_development_dependency "bundler", ">= 2"
|
|
51
|
+
spec.add_development_dependency "polyrun", ">= 2.2.0"
|
|
52
|
+
spec.add_development_dependency "prosopite", "~> 2.0"
|
|
53
|
+
spec.add_development_dependency "rake", "~> 13"
|
|
54
|
+
spec.add_development_dependency "propshaft", ">= 1.0"
|
|
55
|
+
spec.add_development_dependency "factory_bot", "~> 6"
|
|
56
|
+
spec.add_development_dependency "rspec", "~> 3"
|
|
57
|
+
spec.add_development_dependency "rspec-rails", ">= 6"
|
|
58
|
+
spec.add_development_dependency "sqlite3", ">= 1"
|
|
59
|
+
spec.add_development_dependency "rubocop-rails", "~> 2.34"
|
|
60
|
+
spec.add_development_dependency "rubocop-rspec", "~> 3.8"
|
|
61
|
+
spec.add_development_dependency "rubocop-thread_safety", "~> 0.7"
|
|
62
|
+
spec.add_development_dependency "standard", "~> 1.52"
|
|
63
|
+
spec.add_development_dependency "standard-custom", "~> 1.0"
|
|
64
|
+
spec.add_development_dependency "standard-performance", "~> 1.8"
|
|
65
|
+
spec.add_development_dependency "standard-rails", "~> 1.5"
|
|
66
|
+
spec.add_development_dependency "standard-rspec", "~> 0.3"
|
|
67
|
+
spec.add_development_dependency "rbs", "~> 3"
|
|
68
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
ActiveAdmin.register ActiveAdmin::Annotations::Review, as: "annotation_reviews" do
|
|
4
|
+
menu priority: 3,
|
|
5
|
+
label: "Review",
|
|
6
|
+
if: proc { ActiveAdmin::Annotations.review_menu_visible.call(current_user) },
|
|
7
|
+
menu_name: :auxiliary
|
|
8
|
+
|
|
9
|
+
controller do
|
|
10
|
+
def scoped_collection
|
|
11
|
+
super.includes(:annotations)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
actions :index, :show, :create, :update
|
|
16
|
+
|
|
17
|
+
permit_params :review_status, :notes, :metadata_json
|
|
18
|
+
|
|
19
|
+
filter :subject_type, as: :select
|
|
20
|
+
filter :subject_id
|
|
21
|
+
filter :reviewer_id, label: "Reviewer"
|
|
22
|
+
filter :review_status, as: :select, collection: ActiveAdmin::Annotations::Review::REVIEW_STATUSES
|
|
23
|
+
filter :context_digest
|
|
24
|
+
filter :created_at
|
|
25
|
+
|
|
26
|
+
scope :all, default: true
|
|
27
|
+
scope :pending
|
|
28
|
+
scope :reviewed
|
|
29
|
+
scope :escalated
|
|
30
|
+
scope :needs_follow_up
|
|
31
|
+
|
|
32
|
+
index do
|
|
33
|
+
selectable_column
|
|
34
|
+
id_column
|
|
35
|
+
column :subject_type
|
|
36
|
+
column :subject_id
|
|
37
|
+
column :reviewer
|
|
38
|
+
column :review_status
|
|
39
|
+
column("Annotations") { |review| review.annotations.size }
|
|
40
|
+
column :context_digest
|
|
41
|
+
column :created_at
|
|
42
|
+
actions
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
show do
|
|
46
|
+
attributes_table_for(resource) do
|
|
47
|
+
row :id
|
|
48
|
+
row :subject do |review|
|
|
49
|
+
label = "#{review.subject_type} ##{review.subject_id}"
|
|
50
|
+
subject = review.subject
|
|
51
|
+
if subject.present?
|
|
52
|
+
auto_link(subject, label)
|
|
53
|
+
else
|
|
54
|
+
label
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
row :reviewer
|
|
58
|
+
row :review_status
|
|
59
|
+
row :notes
|
|
60
|
+
row :context_digest
|
|
61
|
+
if ActiveAdmin::Annotations::ContentRevision.enabled_for?(resource.subject)
|
|
62
|
+
row :content_revision_version
|
|
63
|
+
end
|
|
64
|
+
row("Context stale?") { |review| review.context_stale? ? "yes" : "no" }
|
|
65
|
+
row :created_at
|
|
66
|
+
row :updated_at
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
panel "Context" do
|
|
70
|
+
pre JSON.pretty_generate(resource.context_json)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
panel "Annotations" do
|
|
74
|
+
if resource.annotations.any?
|
|
75
|
+
table_for(resource.annotations.order(:created_at)) do
|
|
76
|
+
column :field_name
|
|
77
|
+
column :selected_text
|
|
78
|
+
column :comment
|
|
79
|
+
column :category
|
|
80
|
+
if ActiveAdmin::Annotations::ContentRevision.enabled_for?(resource.subject)
|
|
81
|
+
column :content_revision_version
|
|
82
|
+
end
|
|
83
|
+
column :start_offset
|
|
84
|
+
column :end_offset
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
para "No annotations yet."
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
collection_action :export_jsonl, method: :get do
|
|
93
|
+
exporter = ActiveAdmin::Annotations::Exporter.new(reviews: collection)
|
|
94
|
+
send_data exporter.to_jsonl,
|
|
95
|
+
filename: "annotation-reviews-#{Time.current.strftime("%Y%m%d%H%M%S")}.jsonl",
|
|
96
|
+
type: "application/jsonl"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
action_item :export_jsonl, only: :index do
|
|
100
|
+
link_to "Export JSONL", export_jsonl_admin_annotation_reviews_path(request.query_parameters), class: "action-item-button"
|
|
101
|
+
end
|
|
102
|
+
end
|