---
name: bulk-edit
description: "Fix content and clean up course pages in bulk: document-grounded batch edits, copy pages across modules, or fix accessibility/branding issues."
---

# Bulk Edit

> **Plugin**: ASU Canvas Course Builder
> **Scripts**: `scripts/staging_manager.py`, `scripts/push_to_canvas.py`, `scripts/post_write_verify.py`, `scripts/canvas_api.py`

## Purpose
> **Edit discipline**: Preserve original structure and content in bulk operations. Replace only what the user explicitly targeted. Do not delete placeholders, instructor notes, or scaffolding unless told to.

One skill for all multi-page content modifications: apply content from a source document across pages, copy a page template across modules with variable substitution, bulk-fix accessibility and branding issues, or clean up leftover template content from wrong courses.

---

## Course Confirmation (All Modes)

Before any bulk operation:

1. Read the active course from `.env`. Fetch the course name via API.
2. Confirm: "You're connected to **[Course Name]**. Is this the right course?"
3. Show the course tree (modules → items) so the user can identify targets.
4. Ask: "Which pages do you want to edit?" Accept natural language:
   - "All overview pages"
   - "Everything in Module 3"
   - "Modules 1-4 conclusion pages"
   - Or pick specific items by name

### Progress Reporting

During any bulk operation, report progress after each page:

```
✓ Module 1 Overview: heading hierarchy fixed (1/14 pages)
✓ Module 1 Prepare to Learn: alt text added to 3 images (2/14 pages)
  → Module 2 Overview: scanning... (3/14 pages)
```

After all pages are processed:
```
═══ Bulk Edit Complete ═══
Pages modified: 14
Changes made: 23 heading fixes, 8 alt text additions, 5 link text improvements
All changes staged — preview and push via /staging.
```

---

## Mode 1: Batch Edit (Document-Grounded)

Apply per-module content from a source document across matching pages.

### When to Use
- "Add learning objectives to all overview pages"
- "Insert this content from my doc into each module"
- "Add a footer to all pages"
- "Update the header on every page"

### Workflow

1. **Identify source content**: User provides a document, paste, or description. Extract per-module sections.
2. **Identify target pages**: Match user's selection against the course tree. Confirm matched pages.
3. **Generate edits**: For each target page:
   - Fetch current HTML from Canvas
   - Determine insertion point (prepend, append, replace section, after element, wrap)
   - Generate new HTML with source content merged
   - Preserve the existing page structure and styling unless the user explicitly asks for redesign or template normalization
4. **Stage all edits**: `${ASUCB_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}}/bin/python3 scripts/staging_manager.py --course-id {COURSE_ID} --stage --type pages --id {slug} --html-file <path>`
5. **Preview**: Show diffs or screenshots for review
6. **Iterate**: User requests changes → re-stage
7. **Push**: When approved: `${ASUCB_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}}/bin/python3 scripts/push_to_canvas.py --type page --slugs {slug1},{slug2},... --course-id {COURSE_ID}`
8. **Update progress**: After successful push, update build progress for each modified page: `${ASUCB_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}}/bin/python3 scripts/progress_tracker.py --course-id {COURSE_ID} --update {MOD_NUM} {PAGE_TYPE} complete` and log the bulk edit: `${ASUCB_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}}/bin/python3 scripts/progress_tracker.py --course-id {COURSE_ID} --history "bulk-edited" "{N} pages" --reason "{edit_type}"`

### Content Insertion Patterns

| Pattern | Description |
|---|---|
| Prepend | Add content before existing body |
| Append | Add content after existing body (e.g., footer) |
| Replace section | Find a section by heading/comment and replace |
| After element | Insert after a specific HTML element |
| Wrap | Wrap existing content in new container |

---

## Mode 2: Propagate (Copy Page Across Modules)

Copy one page to multiple modules with variable substitution.

### When to Use
- "Copy the overview template to all 8 modules"
- "Create prepare-to-learn pages for modules 1-8 from this template"

### Workflow

1. **Get source page**: Fetch HTML body from Canvas or staged content
2. **Define variables**: Identify placeholders — auto-detect or from `course-config.json`:
   - `{{MODULE_NUM}}` → 1, 2, 3...
   - `{{MODULE_TITLE}}` → from course-config.json
   - `{{OBJECTIVES}}` → formatted objective list from course-config.json
   - `{{TOPIC}}` → module topic slug
3. **Generate copies**: For each target module, substitute variables and generate page HTML
4. **Stage all copies**: One staged file per target slug
5. **Preview → Iterate → Push**

---

## Content Fix Mode

Use Mode 3 for a user request to fix or clean up existing content. Keep the Content Fix discipline: fix only the issues explicitly identified, preserve the original page structure, and never restructure, remove, or rewrite content beyond the targeted fix.

## Mode 3: Content Fix (Bulk Remediation)

Scan and fix accessibility, branding, or structural issues across all pages. Supports 10 fix types.

### Fix Types

| # | Fix Type | What It Does |
|---|----------|-------------|
| 1 | **Heading Hierarchy** | H1→H2 (Canvas reserves H1), fix skipped levels |
| 2 | **Alt Text** | Flag missing alt text; mark decorative images with `alt=""`; flag content images for review |
| 3 | **Link Text** | Replace generic "click here"/"read more" with descriptive text; add `target="_blank" rel="noopener noreferrer"` to external links; add screen reader "(opens in new tab)" |
| 4 | **Color Contrast** | Fix low-contrast inline colors: `#999`→`#767676`, `#aaa`→`#767676`, `#ccc`→`#595959` |
| 5 | **Placeholder Removal** | Find/flag TODO, TBD, PLACEHOLDER, Lorem ipsum, `[bracket placeholders]` |
| 6 | **Transcript/Caption Fixes** | Add expandable transcript containers to media embeds that lack them |
| 7 | **Submission Button Fixes** | Add "Submit Assignment" button linking to Canvas submission URL |
| 8 | **Table Header Fixes** | Convert first-row `<td>` to `<th scope="col">`; add `scope="row"` to row headers |
| 9 | **ARIA Attribute Fixes** | Add `role`, `aria-label`, `aria-live` to interactive elements |
| 10 | **Inline Style Standardization** | Normalize to ASU palette (#8C1D40 maroon, #FFC627 gold, #333 body text) |

### Fix Workflow

1. **Select fix types**: Show the 10 types and ask which to run (by number, name, or "all")
2. **Scan**: Fetch all target pages, analyze HTML
3. **Report findings**:
   ```
   Fix Type              Pages Affected   Issues Found
   ─────────────────────────────────────────────────────
   Heading Hierarchy     4                12
   Alt Text              7                15 (3 decorative, 12 need review)
   Link Text             6                8
   ─────────────────────────────────────────────────────
   Total                 12 pages         47 issues
   ```
4. **Confirm scope**: "Fix all 47 issues across 12 pages?"
5. **Stage fixes**: Generate corrected HTML and stage to `courses/{COURSE_ID}/staging/pages/{slug}.html`
6. **Preview**: Show before/after diffs for sample pages
7. **Push**: When approved, push via `push_to_canvas.py`

> **MUST-2 guardrail — bulk cosmetic re-push of already-live pages.**
> Always fetch the LIVE body from Canvas (via `canvas_api.get_page`) and transform it in place.
> Never re-push a stale local source — it silently reverts live-only edits (faculty tweaks, CMS changes).
> Whole-body diff verification is unreliable on Canvas (entity re-encoding, marker stripping); verify
> field-targeted (substring/count), not whole-body equality.
> For deterministic bulk transforms (e.g., dash removal), use `scripts/repush_transform.py`:
> ```
> python scripts/repush_transform.py --course-id {ID} --type page --slugs slug1,slug2 --transform dedash --confirm-verb push
> ```

### Fix Detail: Key Patterns

**Heading Hierarchy:**
```
Find: <h1>Section Title</h1>     → Fix: <h2 style="color: #8C1D40;">Section Title</h2>
Find: <h4> after <h2> (skipped)  → Fix: Change <h4> to <h3>
```

**Alt Text:**
```
Find: <img src="..." > (no alt)
Fix:  <img src="..." alt="">              (decorative — spacers, borders, icons < 50px)
      <img src="..." alt="[ALT_REVIEW]">  (content images — flag for review)
```
**CRITICAL**: Never auto-generate descriptive alt text. Only mark decorative or flag for human review.

**Link Text:**
```
Find: <a href="url">click here</a>
Fix:  <a href="url" target="_blank" rel="noopener noreferrer">
        Descriptive text <span class="screenreader-only">(opens in new tab)</span>
      </a>
```

**Color Contrast:**
```
#999 → #767676  (4.54:1 on white — passes AA)
#666 → #595959  (7:1 on white — passes AAA)
#aaa → #767676
#ccc → #595959
```

### Safety Rules
- **Never auto-fix alt text content** — only flag or mark decorative
- **Never remove content** — only modify, wrap, or flag
- **Preserve all inline styles** except the specific issue being fixed
- **Large batch warning** at 10+ pages: extra confirmation with domain/instance

---

## Mode 4: Template Cleanup (Wrong-Course Content Removal)

Remove leftover content from a previously imported template that doesn't belong to the current course.

### When to Use
- "Delete the leftover Criminal Justice content"
- "Remove the old discussions that aren't mine"
- "Clean up the template — there's stuff from another course"
- "Delete Videos and Readings pages from all modules"

### Workflow

1. **Identify foreign content**: Fetch all modules, pages, discussions, quizzes, assignments, and assignment groups. Cross-reference against `course-config.json`.
2. **Show findings** categorized:
   ```
   ═══ Template Cleanup Scan ═══
   Discussions (wrong course):
     - "Fourth Amendment Search & Seizure" (CRJ template)
     - "Police Interrogation Techniques" (CRJ template)

   Placeholder modules:
     - Module 15: Midterm Exam (empty)

   Assignment groups:
     - "Exams" (50% weight, 0 items) — not in course design

   Redundant pages (per module):
     - "Videos" pages in M1–M7
     - "Readings" pages in M1–M7
   ```
3. **Confirm**: "Delete all 14 items? Or select specific categories?"
4. **Execute deletions** using `canvas_api.py` delete helpers:
   - Pages: `delete_page(config, slug)`
   - Module items: `delete_module_item(config, module_id, item_id)`
   - Modules: `delete_module(config, module_id)`
   - Discussions: `delete_discussion(config, topic_id)`
   - Assignment groups: reweight remaining, then delete
5. **Report**: Deletion results with counts per category

### Safety
- Module/item deletions are logged but **not reversible** — always confirm with user
- Assignment group deletion requires checking if it contains assignments first
- Never delete without explicit user confirmation per category

---

## Remediation Brief Mode

Use this mode when the user provides a QA-at-Scale `remediation-brief.json`, says they downloaded a remediation brief from Airtable, or asks for structured remediation from an Initial Review.

### Required Inputs

| Input | Description |
|---|---|
| `remediation-brief.json` | File path or pasted JSON from the QA-at-Scale pipeline |
| Canvas course URL or ID | Must match the brief's `course_id` unless the user explicitly confirms the mismatch |

Validate `schema_version`, `course_id`, `course_name`, `canvas_url`, `audit_run_id`, `report_url`, `prompt_version`, `audit_date`, and `criteria_to_remediate[]` fields when present. If the schema version is missing or newer than expected, warn that some fields may not be recognized.

### Verdict Handling

| Verdict | Action |
|---|---|
| Not Met | Fix the issue using the brief's evidence, suggestion, and affected pages |
| Partially Met | Review existing evidence, then enhance or complete the content |
| Needs Review | Present the evidence and ask the user to decide whether it passes or needs a fix |
| Manual Entry | Skip; tell the user which external tool or dashboard must be checked |

### Remediation Surfaces

Process the brief in three passes:

1. **Course-level settings**: navigation tabs, home page, grading scheme, assignment group weights, grading policies.
2. **Canvas object settings**: assignment, quiz, discussion, rubric, module, item order, publication, due-date, points, time-limit, and attempt settings.
3. **Page content**: page HTML, missing pages, headings, alt text, link text, content quality, and accessibility fixes.

For page content, group criteria by page URL in module order. If a criterion affects multiple pages, include it in each page group but track whether it has already been resolved.

### Execution Procedure

1. Load and validate the brief.
2. Confirm the active course name and id match the brief.
3. Summarize total criteria by verdict, affected surfaces, affected pages/objects, and manual items.
4. Work through course-level settings first, object settings second, and page content last.
5. For settings changes, propose the exact Canvas API update, confirm with the user, apply, and verify.
6. For missing rubrics, quizzes, or discussions, delegate to `rubric-creator`, `quiz-generator`, or `discussion-generator` with the criterion evidence.
7. For page HTML, stage one page at a time, preview, confirm, push, and verify. Do not batch multiple pages into one push.
8. Finish with counts for fixed, improved, investigated, skipped, and remaining criteria, then recommend re-running the QA-at-Scale audit.

### Remediation Brief Safety

- Confirm every Canvas write with course name and specific action.
- Never direct-PUT page bodies; use stage -> preview -> confirm -> push -> verify.
- Do not guess on Needs Review criteria.
- Do not attempt Manual Entry criteria such as Ally dashboard checks.
- Warn loudly on course-id mismatch.
- Preserve existing content and change the minimum HTML needed.
- Add small delays between API calls when processing many pages.

### Edge Cases

- Empty brief: report that all criteria passed and no remediation is needed.
- Page URL returns 404: warn that the page may have been renamed or deleted and ask whether to skip or investigate.
- Brief older than 30 days: warn that the course may have changed and suggest re-auditing first.
- Multiple briefs: process the user-provided file but recommend using the most recent brief.

## Common to All Modes

### Staging Integration

**All bulk edits that touch page HTML body MUST flow through staging — no exceptions:**
1. Generate modified HTML
2. Stage to `courses/{COURSE_ID}/staging/pages/{slug}.html`
3. Preview for user approval
4. Push with backup only after explicit approval

### Post-Push Verification

After any bulk push:
1. **Report Canvas links** for every modified page
2. **Offer screenshots** of a sample (up to 3 pages)
3. **For deletions**: re-fetch module list to confirm items are gone

### Safety
- Nothing touches Canvas until the user approves
- Every push creates a timestamped backup
- User can rollback any page via the staging skill's rollback mode
- Batch operations show a summary before executing
- Document-grounded and remediation edits default to minimal-diff updates, not visual redesign

## Error Handling

| Error | Resolution |
|---|---|
| Page not found | Skip and report, continue with others |
| Source document unclear | Ask user to clarify which content maps to which module |
| HTML parsing error | Show raw HTML, ask user to verify structure |
| Variable not found in config | Ask user to provide value manually |
