Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .claude/skills/blog-post/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ Source and issues at [github.com/prettysmartdev/amux](https://github.com/prettys

**What to include**
- Shell examples with `sh` code blocks for any commands a reader would run
- Screenshot placeholders (e.g. `![TUI showing the new dialog](images/NNNN-slug-01.png)`) when a visual would help — do not attempt ASCII art
- Screenshot placeholders when a visual would help — do not attempt ASCII art. Wrap them in an HTML comment so the docs link check does not flag an image that does not exist yet:
```markdown
<!-- SCREENSHOT PLACEHOLDER: the TUI showing the new dialog.
Save it as docs/blog/images/NNNN-slug-01.png and replace this comment with:
![TUI showing the new dialog](./images/NNNN-slug-01.png) -->
```
- The install snippet (`curl -s https://prettysmart.dev/install/amux.sh | sh`) in the first third of the post, inside a `---` fenced section
- Concrete "before vs. after" framing when the post is about a fix or refactor

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/release-prep/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Create `docs/blog/NNNN-slug.md` where NNNN is the next number after the last pos
- Open with the problem or itch, not the solution
- Explain *why* the feature matters before explaining *what* it does
- Focus on the improved workflows, problems solved, and benefits to security that the tool brings rather than how it works internally
- Show examples with code/shell blocks (or screenshot placeholders for the human to fill in later, don't try to do ASCII art)
- Show examples with code/shell blocks (or screenshot placeholders for the human to fill in later, don't try to do ASCII art). Keep placeholders inside an HTML comment — see the blog-post skill — so the docs link check does not flag an image that does not exist yet
- No buzzwords ("revolutionary", "game-changing", "seamless", "robust")
- No fluff ("In this post I will...", "I'm excited to announce...")
- Inlcude a quick blurb on how to install the tool in the first 1/3 of the post (the curl|sh version)
Expand Down
48 changes: 47 additions & 1 deletion tests/data_layer/rename_0077.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ fn migration_is_noop_when_amux_dir_absent() {
/// link `[text](target)` or `[text](target#anchor)` resolves to a file that
/// exists. This catches broken links introduced by doc renames (e.g.
/// `08-headless-mode.md` → `08-api-mode.md`).
///
/// Content inside `<!-- … -->` is skipped: it never reaches a reader, so a
/// link there cannot be broken for anyone. Screenshot placeholders left for a
/// human to fill in later live in such comments.
#[test]
fn docs_internal_links_all_resolve() {
let docs_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs");
Expand All @@ -315,7 +319,7 @@ fn docs_internal_links_all_resolve() {
.expect("md file must have a parent dir")
.to_path_buf();

for link_target in extract_md_links(&content) {
for link_target in extract_md_links(&strip_html_comments(&content)) {
if link_target.starts_with("http://")
|| link_target.starts_with("https://")
|| link_target.starts_with("mailto:")
Expand Down Expand Up @@ -351,6 +355,30 @@ fn docs_internal_links_all_resolve() {
);
}

#[test]
fn commented_out_links_are_not_checked() {
let content = "\
live [one](./a.md) here
<!-- SCREENSHOT PLACEHOLDER: save it as images/squad-tab.png, then use:
![squad tab](./images/squad-tab.png) -->
live [two](./b.md) here
";
let targets = extract_md_links(&strip_html_comments(content));
assert_eq!(
targets,
vec!["./a.md".to_string(), "./b.md".to_string()],
"links inside <!-- --> must not be treated as live links"
);
}

#[test]
fn strip_html_comments_handles_multiple_and_unterminated_spans() {
assert_eq!(strip_html_comments("a<!--x-->b<!--y-->c"), "abc");
assert_eq!(strip_html_comments("no comments here"), "no comments here");
// An unterminated comment swallows the rest of the input.
assert_eq!(strip_html_comments("keep<!--dropped [x](./y.md)"), "keep");
}

fn collect_md_files_recursively(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut out = Vec::new();
let entries = match std::fs::read_dir(dir) {
Expand All @@ -368,6 +396,24 @@ fn collect_md_files_recursively(dir: &std::path::Path) -> Vec<std::path::PathBuf
out
}

/// Remove every `<!-- … -->` span from a Markdown string so that commented-out
/// content is not mistaken for a live link. An unterminated `<!--` swallows the
/// rest of the input, which is how Markdown renderers treat it.
fn strip_html_comments(content: &str) -> String {
let mut out = String::with_capacity(content.len());
let mut rest = content;

while let Some(start) = rest.find("<!--") {
out.push_str(&rest[..start]);
match rest[start..].find("-->") {
Some(end) => rest = &rest[start + end + "-->".len()..],
None => return out,
}
}
out.push_str(rest);
out
}

/// Extract all link targets from `[text](target)` patterns in a Markdown string.
/// Does not parse full CommonMark; only handles inline `[…](…)` links.
fn extract_md_links(content: &str) -> Vec<String> {
Expand Down
Loading