Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d75eb8f
Add CAML (Corpus Article Markup Language) interactive article system
claude Mar 24, 2026
5793269
Add Playwright component tests for CAML system and v2 spec
claude Mar 24, 2026
9495fce
Fix tests to pass and add 16 documentation screenshots
claude Mar 24, 2026
8155188
Merge remote-tracking branch 'origin/main' into claude/markdown-inter…
JSv4 Mar 24, 2026
fdb6851
Address code review feedback: security, correctness, and code quality…
JSv4 Mar 24, 2026
7c748de
Merge remote-tracking branch 'origin/main' into claude/markdown-inter…
JSv4 Mar 24, 2026
314638f
Address remaining review feedback: fragment hrefs, ConfirmModal, disa…
JSv4 Mar 24, 2026
5d8ee7a
Address review: replace hardcoded colors, fix YAML parser bug, DRY ex…
JSv4 Mar 25, 2026
dfbd786
Add unit tests for CAML parser and safeHref URL guard
JSv4 Mar 25, 2026
0adeb29
Add backend tests for MarkdownParser to fix codecov/patch
JSv4 Mar 25, 2026
447ed31
Add design spec for CAML npm library extraction
JSv4 Mar 25, 2026
5f499fe
Fix spec review issues: correct color values, typography, prop threading
JSv4 Mar 25, 2026
21a7e5b
Add implementation plan for CAML npm extraction
JSv4 Mar 25, 2026
df5ab5f
Fix plan review issues: DeepPartial export, counts, import path comments
JSv4 Mar 25, 2026
0a8c8d3
Switch to @os-legal/caml and @os-legal/caml-react packages
JSv4 Mar 26, 2026
3bc505a
Add implementation plan for CAML npm migration
JSv4 Mar 27, 2026
3f8fa37
Switch @os-legal/caml packages from link: to npm ^0.0.1
JSv4 Mar 27, 2026
8085900
Fix CAML test imports to use @os-legal/caml npm packages
JSv4 Mar 27, 2026
7fb7bbb
Wire corpus stats through to CAML article renderer
JSv4 Mar 27, 2026
fbda2ab
Update CAML editor template with case-history and map blocks
JSv4 Mar 27, 2026
0c66b95
Add map and case-history blocks to CAML test fixture with screenshots
JSv4 Mar 27, 2026
1eace5f
Add editor screenshot test for map and case-history template blocks
JSv4 Mar 27, 2026
8a2f86c
Show CAML article as landing view with floating controls when Readme.…
JSv4 Mar 27, 2026
75ed182
Add Playwright test for CAML article as corpus landing view with floa…
JSv4 Mar 27, 2026
8a2a556
Merge remote-tracking branch 'origin/main' into claude/markdown-inter…
JSv4 Mar 27, 2026
aebcc6d
Fix pyupgrade: remove redundant utf-8 encoding argument
JSv4 Mar 27, 2026
ac5eb3e
Fix Vitest: inline @os-legal/caml-react for styled-components ESM compat
JSv4 Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **CAML Interactive Article System** (PR #1156): Full-stack support for corpus articles using CAML (Corpus Article Markup Language). Includes a two-pass parser (tokenizer + block parsers), typed intermediate representation, and composable renderer supporting hero sections, cards, pills, tabs, timelines, CTAs, signup blocks, corpus stats, and pullquotes. Backend adds `MarkdownParser` pipeline component (`opencontractserver/pipeline/parsers/oc_markdown_parser.py`) with `text/markdown` MIME type detection, `text_to_frontmatter` title filter on `DocumentFilter`, and centralized `TEXT_MIMETYPES` constant (`opencontractserver/constants/document_processing.py`). Frontend adds `CamlArticleEditor` modal with live preview, `CorpusArticleView` for rendered article display, and `CorpusLandingView` integration for article discovery. Comprehensive unit tests for parser and `safeHref` XSS guard (34 tests), plus Playwright component tests with `docScreenshot` captures for all block types.

### Fixed

- **CAML YAML parser nested key bug**: Fixed `parseYamlFrontmatter` in `frontend/src/caml/parser/tokenizer.ts` where `content` used `line.trimEnd()` instead of `line.trimEnd().trimStart()`, causing the key-value regex (`^[a-zA-Z_]...`) to fail for indented nested keys (e.g., `hero.kicker`). Nested frontmatter properties were silently dropped, producing empty objects.

### Changed

- **Replaced hardcoded hex colors with OS_LEGAL_COLORS tokens** in `CamlArticleEditor.tsx` and `CorpusArticleView.tsx`: All hardcoded hex values (`#e2e8f0`, `#fafbfc`, `#f8fafc`, `#64748b`, `#94a3b8`, `#ffffff`, `#fef3c7`, `#92400e`, `#475569`, `#f1f5f9`, `#cbd5e1`) replaced with semantic design tokens from `osLegalStyles.ts`.
- **Extracted `isExternalHref` helper** in `frontend/src/caml/renderer/safeHref.ts`: Deduplicated `href.startsWith("http")` checks across `CamlBlocks.tsx` and `CamlFooter.tsx` into a shared utility function.
- **Removed redundant `articleStats` useMemo** in `CorpusArticleView.tsx`: The memo mirrored its input without transformation; `stats` is now passed directly to `CamlArticle`.

### Fixed

- **Annotation rendering cleanup** (Closes #1144): Replaced hardcoded `"4px 4px 0 0"` border-radius with `ANNOTATION_BOUNDARY_RADIUS` constant in `SearchResult.tsx` and `ChatSourceResult.tsx`. Removed dead `border` prop from `SelectionInfo` interface and call sites. Used `APPROVED_RGB` constant for approved-state box-shadow in `SelectionBoundary.tsx` (matching `REJECTED_RGB` pattern). Added `TOKEN_EXPANSION_PX` constant and replaced magic `-1`/`+2` token expansion values across `SelectionTokenGroup.tsx`, `Tokens.tsx`, and `ChatSourceTokens.tsx`. Consolidated `[Previous Unreleased]` CHANGELOG section into single `[Unreleased]` block. Removed debug `console.log` statements from `DocumentKnowledgeBase.ct.tsx`, `SearchResult.tsx`, and `SelectionTokenGroup.tsx`. Moved annotation display reactive var initialization into `useEffect` in `DocumentKnowledgeBaseTestWrapper.tsx`.
Expand Down
8 changes: 7 additions & 1 deletion config/graphql/document_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,13 @@ def mutate(
if kind is None:

if is_plaintext_content(file_bytes):
kind = "text/plain"
# Detect markdown/CAML files by extension
if filename and filename.lower().endswith(
(".caml", ".md", ".markdown")
):
kind = "text/markdown"
else:
kind = "text/plain"
else:
return UploadDocument(
message="Unable to determine file type", ok=False, document=None
Expand Down
1 change: 1 addition & 0 deletions config/graphql/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ class Meta:
fields = {
"description": ["exact", "contains"],
"id": ["exact"],
"title": ["exact", "contains"],
}


Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading