Skip to content

Commit fd8cb51

Browse files
pfranktestingclaude
andcommitted
Bump to v1.4.1: beta tester feedback and alphabetical order check
- Fix dead link in Reference authors help (author-format → elements-list-entry) - Improve reference parser to classify Frontiers/PLoS volume:article format and Available-at entries, resolving spurious review status on italics check - Clarify Reference title capitalization howToFix with two direct instructions - Add journal title abbreviation note to Reference italics expectedItems - Update Reference punctuation instructions with two-case volume/issue/page format examples and article number case - Reorder report categories: Paper formatting → Citations → References - Remove misleading alphabetical order text from References page expected text - Add checkReferencesAlphabeticalOrder check (A–Z by first author surname) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9c71bd7 commit fd8cb51

4 files changed

Lines changed: 108 additions & 18 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A client-side tool for checking APA 7 formatting in Word documents. Upload a `.d
66

77
> **Note:** APA Coach is in active development. Always review your paper manually before submitting.
88
9-
**Version 1.4.0** — Last updated June 27, 2026
9+
**Version 1.4.1** — Last updated July 3, 2026
1010

1111
Contact pfrank@aiuniv.edu with questions, comments, or bug reports.
1212

@@ -146,6 +146,9 @@ See the [LICENSE](LICENSE) file for details.
146146

147147
## Changelog
148148

149+
**v1.4.1** — July 3, 2026
150+
Beta tester feedback round: improved reference parser to correctly classify Frontiers/PLoS-style journal articles (volume:article format) and "Available at:" entries, resolving spurious "review" status on the Reference italics check. Added new References alphabetical order check. Reordered report categories to Paper formatting → Citations → References. Updated instructions for Reference title capitalization, Reference italics, Reference punctuation, and References page checks for clarity. Fixed dead link in Reference authors help resources.
151+
149152
**v1.4.0** — June 27, 2026
150153
Added 9 new checks based on library reviewer testing: heading numbering, heading capitalization, heading bold, heading level alignment, references start on new page, references numbered, citation comma, reference DOI format, and reference forbidden phrases. Fixed false positives in citation matching, reference italics, and title-case detection. Renamed "Inline citations" to "In-text citations". Full details in [edgecases.md](edgecases.md) and [CHANGELOG.md](CHANGELOG.md).
151154

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "apa-coach",
3-
"version": "1.4.0",
3+
"version": "1.4.1",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

src/browser/main.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import "./styles.css";
77

88
const APP_INFO = {
99
version: packageInfo.version,
10-
lastUpdated: "June 27, 2026",
10+
lastUpdated: "July 3, 2026",
1111
supportEmail: "pfrank@aiuniv.edu",
1212
resources: [
1313
{
@@ -70,6 +70,7 @@ const CHECK_CATEGORY = {
7070
"Unconverted markup symbols": "Paper formatting",
7171
// References
7272
"References page": "References",
73+
"References alphabetical order": "References",
7374
"References start on new page": "References",
7475
"References numbered": "References",
7576
"References heading alignment": "References",
@@ -101,7 +102,7 @@ const CHECK_CATEGORY = {
101102
"Secondary citations": "Citations",
102103
};
103104

104-
const CATEGORY_ORDER = ["Paper formatting", "References", "Citations"];
105+
const CATEGORY_ORDER = ["Paper formatting", "Citations", "References"];
105106

106107
function sortChecks(checks) {
107108
return [...checks].sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);

src/checks/checkApaFormatting.js

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const APA_REFERENCE_FORMAT_RESOURCE = {
6969
};
7070
const APA_AUTHORS_RESOURCE = {
7171
label: "APA Style: Author Format",
72-
url: "https://apastyle.apa.org/style-grammar-guidelines/references/author-format",
72+
url: "https://apastyle.apa.org/style-grammar-guidelines/references/elements-list-entry",
7373
};
7474
const APA_DOI_FORMAT_RESOURCE = {
7575
label: "APA Style: DOIs and URLs",
@@ -538,11 +538,18 @@ function getHowToFix(rule) {
538538
];
539539
}
540540

541+
if (rule === "References alphabetical order") {
542+
return [
543+
"Sort all references A–Z by the first author's last name.",
544+
"If two entries share the same first author, sort by the second author's last name, then by year.",
545+
"Group authors (e.g., organizations) sort by the first word of their name.",
546+
];
547+
}
548+
541549
if (rule === "References page") {
542550
return [
543551
"Add a new page at the end of the document titled 'References'.",
544552
"Center the word 'References' at the top of the page.",
545-
"List all sources in alphabetical order.",
546553
"Use a hanging indent for each reference entry.",
547554
];
548555
}
@@ -569,7 +576,8 @@ function getHowToFix(rule) {
569576

570577
if (rule === "Reference title capitalization") {
571578
return [
572-
"Reference titles use sentence case: capitalize only the first word, the first word after a colon, and proper nouns.",
579+
"Capitalize the first letter of the first word of the title.",
580+
"Capitalize the first letter of the first word of the subtitle (after the colon).",
573581
"Example — correct: \"The effects of social media on academic performance\"",
574582
"Example — incorrect: \"The Effects of Social Media on Academic Performance\"",
575583
"Proper nouns (names of people, places, organizations, brands) stay capitalized: \"Facebook\", \"United States\", \"COVID-19\".",
@@ -590,15 +598,19 @@ function getHowToFix(rule) {
590598
"Book, report, and standalone work titles should be italicized.",
591599
"Journal article and book chapter titles should NOT be italicized — remove any italic formatting.",
592600
"For journal articles: italicize only the journal name and volume number. The issue number in parentheses and page range should not be italic.",
601+
"Write journal titles in full — do not abbreviate: \"International Journal of Information and Learning Technology\", not \"Int. J. Inf. Learn. Technol.\"",
593602
"In Word, select the text and press Ctrl+I (Cmd+I on Mac) to toggle italic.",
594603
];
595604
}
596605

597606
if (rule === "Reference punctuation") {
598607
return [
599-
"Each reference must end with the URL or DOI on its last line.",
608+
"Check for the volume, issue, and page numbers for each article.",
609+
"If an article has a volume, issue, and pages: Journal Title, 11(1), 99–110.",
610+
"If an article does not have an issue number: Journal Title, 11, 99–110.",
611+
"If an article has an article number instead of pages: Journal Title, 11(1), Article e0306639.",
600612
"Journal page ranges do not use \"p.\" or \"pp.\" — write the page range directly: 45–67, not pp. 45–67.",
601-
"Write volume and issue with no space: 15(3) not 15 (3).",
613+
"If there is an issue number, write volume and issue with no space: 15(3) not 15 (3).",
602614
"Use an en dash (–) for page ranges, not a hyphen (-).",
603615
];
604616
}
@@ -1398,11 +1410,23 @@ function classifyReferenceKind(parsed) {
13981410
const source = parsed.sourcePart || "";
13991411
const title = parsed.title || "";
14001412

1401-
// Journal article: "Volume(Issue), pp" or "Volume, pp" pattern in source
1413+
// Journal article: "Volume(Issue)" or "Volume, page-range" pattern in source
14021414
if (/\b\d+\s*\(\s*\d+\s*\)/.test(source) || /,\s*\d+\s*(?:[-]\s*\d+)\b/.test(source)) {
14031415
return "journal-article";
14041416
}
14051417

1418+
// Journal article: Frontiers/PLoS-style "Volume:ArticleID" (e.g., "Front. Educ.8:1206936", "PLoS ONE19:e0306639")
1419+
// No \b before the digit — the number is often concatenated directly to the journal name abbreviation.
1420+
if (/\d+:[0-9a-zA-Z]+/.test(source)) {
1421+
return "journal-article";
1422+
}
1423+
1424+
// Journal article: "Journal, Volume, ArticleNumber" where article number is 5+ digits
1425+
// (used by some journals instead of a page range, e.g., "Sustainable Futures, 10, 101408")
1426+
if (/,\s*\d+,\s*\d{5,}\b/.test(source)) {
1427+
return "journal-article";
1428+
}
1429+
14061430
// Book chapter: "In Editor (Ed.), Book Title" or bare "In Book Title. Publisher."
14071431
if (/\bIn\s+[^.]*\(Eds?\.\)/i.test(source) || /\bIn\s+[^.]*\(Eds?\.\)/i.test(title)) {
14081432
return "book-chapter";
@@ -1411,9 +1435,12 @@ function classifyReferenceKind(parsed) {
14111435
return "book-chapter";
14121436
}
14131437

1414-
// Webpage: URL present, no source signals — but doi.org links are journal articles
1415-
if (parsed.doiOrUrl && !source) {
1416-
return /\bdoi\.org\/10\./i.test(parsed.doiOrUrl) ? "journal-article" : "webpage";
1438+
// Webpage: URL present with no source, or with "Available at:" in source (reports, white papers)
1439+
if (parsed.doiOrUrl) {
1440+
const isDoi = /\bdoi\.org\/10\./i.test(parsed.doiOrUrl);
1441+
if (!source || /\bavailable\s+at\b/i.test(source)) {
1442+
return isDoi ? "journal-article" : "webpage";
1443+
}
14171444
}
14181445

14191446
// Book: has a publisher-like tail and no journal numbers
@@ -1683,8 +1710,8 @@ function checkReferencesPage(extracted) {
16831710
rule: "References page",
16841711
status,
16851712
passed: status === "pass",
1686-
expected: "APA expects a References page with sources listed in alphabetical order using hanging indents.",
1687-
expectedText: "APA expects a References page with sources listed in alphabetical order using hanging indents.",
1713+
expected: "APA requires a References section at the end of the document with the heading 'References'.",
1714+
expectedText: "APA requires a References section at the end of the document with the heading 'References'.",
16881715
foundText:
16891716
status === "fail"
16901717
? "APA Coach did not detect a References page at the end of the document."
@@ -2682,6 +2709,7 @@ function checkReferenceItalics(extracted, referencesHeading) {
26822709
"Books and reports: the title should be in italics.",
26832710
{ text: "Book *chapters*, if used: do not italicize the chapter title.", sub: true },
26842711
"Journal articles: do not italicize the article title. Italicize the journal name and the volume number — but not the issue number (the number in parentheses).",
2712+
"Write journal names in full — do not abbreviate: \"International Journal of Information and Learning Technology\", not \"Int. J. Inf. Learn. Technol.\"",
26852713
];
26862714
const referenceParagraphs = getReferenceEntryParagraphs(extracted.paragraphs, referencesHeading);
26872715
const entryParagraphs = getMergedReferenceEntries(referenceParagraphs);
@@ -2786,9 +2814,63 @@ function checkReferenceItalics(extracted, referencesHeading) {
27862814
};
27872815
}
27882816

2817+
function checkReferencesAlphabeticalOrder(extracted, referencesHeading) {
2818+
const rule = "References alphabetical order";
2819+
const expected = "APA requires references to be listed in alphabetical order by the first author's last name.";
2820+
const referenceParagraphs = getReferenceEntryParagraphs(extracted.paragraphs, referencesHeading);
2821+
const entryParagraphs = getMergedReferenceEntries(referenceParagraphs);
2822+
2823+
if (entryParagraphs.length < 2) {
2824+
return {
2825+
rule, status: "review", passed: false, expected, expectedText: expected,
2826+
foundText: entryParagraphs.length === 0
2827+
? "No reference entries found."
2828+
: "Only one reference entry found — alphabetical order cannot be verified.",
2829+
applicable: 0, checked: 0, matched: 0, failed: 0, unknown: 0,
2830+
found: "Too few entries", applicableParagraphs: 0, details: [], howToFix: [], resources: [],
2831+
};
2832+
}
2833+
2834+
const keyed = entryParagraphs.map(p => {
2835+
const parsed = parseReferenceEntry(p);
2836+
const raw = (parsed.authorsRaw || "").trim().replace(/\.\s*$/, "");
2837+
const commaIdx = raw.indexOf(",");
2838+
const sortKey = (commaIdx > 0 ? raw.slice(0, commaIdx) : raw)
2839+
.toLowerCase()
2840+
.replace(/^[^a-z]+/, "");
2841+
return { p, sortKey, text: p.text.trim() };
2842+
});
2843+
2844+
const checkable = keyed.filter(e => e.sortKey.length > 0);
2845+
const unknowns = keyed.filter(e => e.sortKey.length === 0).map(e => e.p);
2846+
2847+
const failures = [];
2848+
const details = [];
2849+
2850+
for (let i = 1; i < checkable.length; i++) {
2851+
const prev = checkable[i - 1];
2852+
const curr = checkable[i];
2853+
if (curr.sortKey < prev.sortKey) {
2854+
if (!failures.includes(curr.p)) failures.push(curr.p);
2855+
const prevPreview = prev.text.length > 50 ? prev.text.slice(0, 50) + "…" : prev.text;
2856+
const currPreview = curr.text.length > 50 ? curr.text.slice(0, 50) + "…" : curr.text;
2857+
details.push(`Out of order: "${currPreview}" should come before "${prevPreview}"`);
2858+
}
2859+
}
2860+
2861+
const foundText = failures.length > 0
2862+
? `${failures.length} reference${failures.length === 1 ? " appears" : "s appear"} to be out of alphabetical order.`
2863+
: unknowns.length > 0
2864+
? `Alphabetical order appears correct for ${checkable.length} ${checkable.length === 1 ? "entry" : "entries"}; ${unknowns.length} could not be verified.`
2865+
: "References appear to be in alphabetical order.";
2866+
2867+
return finishCheck(rule, expected, foundText, entryParagraphs, failures, unknowns, details,
2868+
getHowToFix(rule), [APA_REFERENCE_FORMAT_RESOURCE]);
2869+
}
2870+
27892871
function checkReferencePunctuation(extracted, referencesHeading) {
27902872
const rule = "Reference punctuation";
2791-
const expected = "For journal articles, APA has two punctuation rules for the volume, issue, and page range:";
2873+
const expected = "For journal articles, APA formats the volume, issue, and page range as follows — the issue number in parentheses is only included when present:";
27922874
const referenceParagraphs = getReferenceEntryParagraphs(extracted.paragraphs, referencesHeading);
27932875
const entryParagraphs = getMergedReferenceEntries(referenceParagraphs);
27942876

@@ -2834,8 +2916,10 @@ function checkReferencePunctuation(extracted, referencesHeading) {
28342916
: "Reference punctuation appears correct.";
28352917

28362918
const expectedItems = [
2837-
"Write the volume and issue together with no space: *15*(3) — not *15* (3).",
2838-
"Write the page range directly after the issue: *15*(3), 45–67 — do not add \"p.\" or \"pp.\" before the numbers.",
2919+
"With volume, issue, and pages: Journal Title, *11*(1), 99–110.",
2920+
"Without an issue number: Journal Title, *11*, 99–110.",
2921+
"With an article number instead of pages: Journal Title, *11*(1), Article e0306639.",
2922+
"Do not add \"p.\" or \"pp.\" before the page range.",
28392923
];
28402924

28412925
return {
@@ -3544,6 +3628,7 @@ function checkApaFormatting(extracted) {
35443628
checkReferenceForbiddenPhrases(extracted, referencesHeading),
35453629
checkReferenceShortLinks(extracted, referencesHeading),
35463630
checkUnapprovedSources(extracted, referencesHeading),
3631+
checkReferencesAlphabeticalOrder(extracted, referencesHeading),
35473632
checkReferenceAuthors(extracted, referencesHeading),
35483633
checkReferenceYear(extracted, referencesHeading),
35493634
checkReferenceTitleCapitalization(extracted, referencesHeading),
@@ -3592,6 +3677,7 @@ module.exports = {
35923677
checkUnconvertedMarkup,
35933678
parseReferenceEntry,
35943679
classifyReferenceKind,
3680+
checkReferencesAlphabeticalOrder,
35953681
checkReferenceAuthors,
35963682
checkReferenceYear,
35973683
checkReferenceTitleCapitalization,

0 commit comments

Comments
 (0)