Repository navigation
v1.6.0 — Architecture refactor
jarkdown-rs v1.6.0 — Architecture refactor release
This release closes a seven-issue architecture refactor (#7, #9, #10, #11, #12, #8, #13) that reshapes the crate's internals — typed wire models at the HTTP seam, a pure rendering function, smaller single-purpose modules, and a testable seam in hierarchical export — without changing CLI behavior or on-disk output.
Compatibility for CLI users: every export against the live regression harness is byte-identical to 1.5.0 across full, hierarchical, bulk, and changelog scenarios, including --strict-md byte-equality on the rendered Markdown. Drop-in upgrade.
Compatibility for library consumers: the public MarkdownConverter struct is removed; switch to jarkdown::compose(&RenderContext). See Breaking changes below.
Highlights
- Typed wire models at the Jira HTTP seam.
fetch_issuereturnsIssue,search_jqlreturnsVec<IssueSearchResult>,fetch_changelogreturnsVec<ChangelogEntry>.jira_client.rsis pure transport again. The full Issue surface is typed — spine fields, comments, the 19 display-only standard fields, worklog entries, components/versions/fixVersions, project, priority, resolution, votes, watches, environment, reporter, creator — backed by 10 small types and hand-rolledfrom_valueparsers that mirror the pattern set by #7. Every typed model retains its rawserde_json::Valueso--include-jsonoutput remains byte-identical (see ADR-0002). - Pure rendering.
MarkdownConverteris gone. The render path is now the single functioncompose(&RenderContext) -> Stringwith no&mut, no render-time mutation, and no hidden state.RenderContext<'a>is a plain pub-fields struct, literal-constructible, that borrows everything it needs. src/markdown.rssplit (1,447 lines → 5 focused modules). New module tree:markdown/mod.rs—RenderContext,CustomFieldMetadata,compose()markdown/adf.rs— ADF → Markdownmarkdown/html.rs— HTML → Markdownmarkdown/attachments.rs—AttachmentIndex+replace_attachment_linksmarkdown/sections.rs— per-section composer functions over&RenderContext
IssueExportertrait seam for hierarchical export. Hierarchical traversal no longer depends on the concrete ten-input export function — it depends on a one-methodIssueExportertrait. Production usesWorkflowIssueExporter; tests use a recording fake. Three new unit tests cover cycle detection, max-depth cap, and max-issues cap — the first real test coverage ofhierarchy.rs.- Single-owner Changelog artifact writes.
changelog::write_artifactsis now the sole place.changelog.mdand.changelog.jsonget written. Eliminates a class of silent-swallow drift between the per-issue export and bulk paths. - Freshness as its own module. The
--incrementaldecision lives insrc/freshness.rs(freshness::plan() -> ExportPlan). The CLI handler and the bulk loop both consult one implementation of ADR-0001 Decision 2. - Field cache pre-resolution. Custom-field name/schema resolution happens once in
src/export.rsbeforeRenderContextis built. The render path never mutatesFieldMetadataCache.
Breaking changes
| Removed | Replacement |
|---|---|
pub use markdown::MarkdownConverter; |
jarkdown::compose(&RenderContext) |
MarkdownConverter::new(...) + compose_markdown(&mut self, ...) |
Literal-construct RenderContext and call compose(&ctx) |
pub fn convert_html_to_markdown / pub fn replace_attachment_links / pub fn parse_adf_to_markdown from the crate root |
All three are now pub(crate) — internal only |
CLI users are not affected — flags, output filenames, and byte-level output are unchanged.
Library consumers should construct a RenderContext directly. The new pattern (see src/export.rs for the production usage):
use jarkdown::{compose, AttachmentIndex, CustomFieldMetadata, RenderContext};
use jarkdown::config::FieldFilter;
let field_metadata = CustomFieldMetadata::empty();
let field_filter = FieldFilter::default();
let attachments = AttachmentIndex::build(&downloaded, skipped_slice);
let ctx = RenderContext {
issue: &issue,
downloaded: &downloaded,
skipped_attachments: skipped_slice,
attachments,
field_metadata: &field_metadata,
field_filter: &field_filter,
child_issues: &[],
changelog_summary: None,
base_url: &api_client.base_url,
domain: &api_client.domain,
};
let markdown = compose(&ctx);New public API
jarkdown::compose(&RenderContext) -> Stringjarkdown::RenderContext<'a>(plain pub-fields, literal-constructible)jarkdown::AttachmentIndex<'a>withbuild()/empty()jarkdown::CustomFieldMetadatawithempty()jarkdown::exporter::{IssueExporter, WorkflowIssueExporter}jarkdown::{ChangelogEntry, ChangelogItem}(re-exported fromissue)- 19 newly-typed fields on
Issueand supporting nested types:Priority,Resolution,Project,NamedRef,TimeTracking,Progress,Votes,Watches,WorklogPage,WorklogEntry
Internals — what didn't change
- All Jira HTTP calls and their semantics.
--include-json(typed models retainraw: Value; serialization byte-identical)..changelog.md/.changelog.jsonartifact shape (ADR-0001).Issue::field()andIssue.raw— still present, now reserved forcustomfield_*access only.--incrementalpolicy (just lives in its own module now).- Attachment lookup semantics (id-first, name lowercased+trimmed, dual-name for downloaded, single-name for skipped).
Testing & verification
cargo test34 / 34 passing — up from ~20 at 1.5.0. New coverage:- 4 tests for typed
Issue/IssueSearchResult/Commentparsing. - 2 tests for typed
ChangelogEntryparsing (positive + malformed-payload rejection). - 2 tests for typed display-field parsing (positive + wrong-typed rejection).
- 3 tests for hierarchical export traversal via a fake
IssueExporter(cycle, max-depth, max-issues). - 2 new section unit tests built from a literal
RenderContext(comments, frontmatter) — nojson!()fixtures.
- 4 tests for typed
cargo clippy --all-targets -- -D warningsclean.- Live regression: every refactor commit was verified byte-identical against a frozen pre-refactor binary (
.goal-baseline/jarkdown-main) across full (PSOP-style ticket), hierarchical (epic → children), and bulk scenarios, including the strict--strict-mdMarkdown byte-equality gate introduced by #8.
Architecture notes / ADRs
- docs/adr/0001-changelog-export.md —
.changelog.md/.changelog.jsonare sibling artifacts;--incrementalbackfills when missing. Untouched by this refactor. - docs/adr/0002-typed-issue-model-retains-raw-payload.md — every typed wire model retains its
raw: Value. Tracked in git as part of this release; was authored alongside #7 last cycle.
Closed issues
| # | Title | Commit |
|---|---|---|
| #7 | Introduce a typed Issue model at the Jira client seam | 33dcfa8 |
| #9 | Consolidate Changelog artifact writes into one module | 254864a |
| #10 | Extract a freshness module that owns the incremental export decision | 4dbd10f |
| #11 | Give hierarchy.rs an IssueExporter seam instead of a direct call | c25022a |
| #12 | Type Changelog entries at the fetch_changelog seam | 9e2825d |
| #8 | Bundle markdown rendering inputs into a RenderContext and split the module | 6acc368 |
| #13 | Type the ~19 display-only Issue fields | 9dbc088 |
Install jarkdown 1.6.0
Shell script (macOS / Linux)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dchuk/jarkdown-rs/releases/download/v1.6.0/jarkdown-installer.sh | shHomebrew
brew install dchuk/tap/jarkdown
# or, if already installed:
brew upgrade jarkdownCargo
cargo install jarkdownDownload jarkdown 1.6.0
| File | Platform |
|---|---|
jarkdown-aarch64-apple-darwin.tar.xz |
Apple Silicon macOS |
jarkdown-x86_64-apple-darwin.tar.xz |
Intel macOS |
jarkdown-aarch64-unknown-linux-gnu.tar.xz |
ARM64 Linux (glibc) |
jarkdown-x86_64-unknown-linux-gnu.tar.xz |
x86_64 Linux (glibc) |
jarkdown-x86_64-pc-windows-msvc.zip |
x86_64 Windows |
SHA-256 sums for each archive are published alongside the archives (*.sha256) plus the aggregate sha256.sum.