mirror of
https://github.com/keathmilligan/obsidian-paste-reformatter.git
synced 2026-07-22 05:43:44 +00:00
chore: cleanup openspec
This commit is contained in:
parent
6be4d6ec56
commit
a05bdc269e
14 changed files with 1 additions and 1081 deletions
19
AGENTS.md
19
AGENTS.md
|
|
@ -1,22 +1,3 @@
|
|||
<!-- OPENSPEC:START -->
|
||||
# OpenSpec Instructions
|
||||
|
||||
These instructions are for AI assistants working in this project.
|
||||
|
||||
Always open `@/openspec/AGENTS.md` when the request:
|
||||
- Mentions planning or proposals (words like proposal, spec, change, plan)
|
||||
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
|
||||
- Sounds ambiguous and you need the authoritative spec before coding
|
||||
|
||||
Use `@/openspec/AGENTS.md` to learn:
|
||||
- How to create and apply change proposals
|
||||
- Spec format and conventions
|
||||
- Project structure and guidelines
|
||||
|
||||
Keep this managed block so 'openspec update' can refresh the instructions.
|
||||
|
||||
<!-- OPENSPEC:END -->
|
||||
|
||||
# Obsidian Plugin Development Guidelines
|
||||
|
||||
This is an Obsidian plugin project. Follow these guidelines when working with the codebase.
|
||||
|
|
|
|||
|
|
@ -1,456 +0,0 @@
|
|||
# OpenSpec Instructions
|
||||
|
||||
Instructions for AI coding assistants using OpenSpec for spec-driven development.
|
||||
|
||||
## TL;DR Quick Checklist
|
||||
|
||||
- Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search)
|
||||
- Decide scope: new capability vs modify existing capability
|
||||
- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`)
|
||||
- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability
|
||||
- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement
|
||||
- Validate: `openspec validate [change-id] --strict` and fix issues
|
||||
- Request approval: Do not start implementation until proposal is approved
|
||||
|
||||
## Three-Stage Workflow
|
||||
|
||||
### Stage 1: Creating Changes
|
||||
Create proposal when you need to:
|
||||
- Add features or functionality
|
||||
- Make breaking changes (API, schema)
|
||||
- Change architecture or patterns
|
||||
- Optimize performance (changes behavior)
|
||||
- Update security patterns
|
||||
|
||||
Triggers (examples):
|
||||
- "Help me create a change proposal"
|
||||
- "Help me plan a change"
|
||||
- "Help me create a proposal"
|
||||
- "I want to create a spec proposal"
|
||||
- "I want to create a spec"
|
||||
|
||||
Loose matching guidance:
|
||||
- Contains one of: `proposal`, `change`, `spec`
|
||||
- With one of: `create`, `plan`, `make`, `start`, `help`
|
||||
|
||||
Skip proposal for:
|
||||
- Bug fixes (restore intended behavior)
|
||||
- Typos, formatting, comments
|
||||
- Dependency updates (non-breaking)
|
||||
- Configuration changes
|
||||
- Tests for existing behavior
|
||||
|
||||
**Workflow**
|
||||
1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context.
|
||||
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes/<id>/`.
|
||||
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement.
|
||||
4. Run `openspec validate <id> --strict` and resolve any issues before sharing the proposal.
|
||||
|
||||
### Stage 2: Implementing Changes
|
||||
Track these steps as TODOs and complete them one by one.
|
||||
1. **Read proposal.md** - Understand what's being built
|
||||
2. **Read design.md** (if exists) - Review technical decisions
|
||||
3. **Read tasks.md** - Get implementation checklist
|
||||
4. **Implement tasks sequentially** - Complete in order
|
||||
5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses
|
||||
6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality
|
||||
7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved
|
||||
|
||||
### Stage 3: Archiving Changes
|
||||
After deployment, create separate PR to:
|
||||
- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/`
|
||||
- Update `specs/` if capabilities changed
|
||||
- Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
|
||||
- Run `openspec validate --strict` to confirm the archived change passes checks
|
||||
|
||||
## Before Any Task
|
||||
|
||||
**Context Checklist:**
|
||||
- [ ] Read relevant specs in `specs/[capability]/spec.md`
|
||||
- [ ] Check pending changes in `changes/` for conflicts
|
||||
- [ ] Read `openspec/project.md` for conventions
|
||||
- [ ] Run `openspec list` to see active changes
|
||||
- [ ] Run `openspec list --specs` to see existing capabilities
|
||||
|
||||
**Before Creating Specs:**
|
||||
- Always check if capability already exists
|
||||
- Prefer modifying existing specs over creating duplicates
|
||||
- Use `openspec show [spec]` to review current state
|
||||
- If request is ambiguous, ask 1–2 clarifying questions before scaffolding
|
||||
|
||||
### Search Guidance
|
||||
- Enumerate specs: `openspec spec list --long` (or `--json` for scripts)
|
||||
- Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available)
|
||||
- Show details:
|
||||
- Spec: `openspec show <spec-id> --type spec` (use `--json` for filters)
|
||||
- Change: `openspec show <change-id> --json --deltas-only`
|
||||
- Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs`
|
||||
|
||||
## Quick Start
|
||||
|
||||
### CLI Commands
|
||||
|
||||
```bash
|
||||
# Essential commands
|
||||
openspec list # List active changes
|
||||
openspec list --specs # List specifications
|
||||
openspec show [item] # Display change or spec
|
||||
openspec validate [item] # Validate changes or specs
|
||||
openspec archive <change-id> [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
|
||||
|
||||
# Project management
|
||||
openspec init [path] # Initialize OpenSpec
|
||||
openspec update [path] # Update instruction files
|
||||
|
||||
# Interactive mode
|
||||
openspec show # Prompts for selection
|
||||
openspec validate # Bulk validation mode
|
||||
|
||||
# Debugging
|
||||
openspec show [change] --json --deltas-only
|
||||
openspec validate [change] --strict
|
||||
```
|
||||
|
||||
### Command Flags
|
||||
|
||||
- `--json` - Machine-readable output
|
||||
- `--type change|spec` - Disambiguate items
|
||||
- `--strict` - Comprehensive validation
|
||||
- `--no-interactive` - Disable prompts
|
||||
- `--skip-specs` - Archive without spec updates
|
||||
- `--yes`/`-y` - Skip confirmation prompts (non-interactive archive)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
openspec/
|
||||
├── project.md # Project conventions
|
||||
├── specs/ # Current truth - what IS built
|
||||
│ └── [capability]/ # Single focused capability
|
||||
│ ├── spec.md # Requirements and scenarios
|
||||
│ └── design.md # Technical patterns
|
||||
├── changes/ # Proposals - what SHOULD change
|
||||
│ ├── [change-name]/
|
||||
│ │ ├── proposal.md # Why, what, impact
|
||||
│ │ ├── tasks.md # Implementation checklist
|
||||
│ │ ├── design.md # Technical decisions (optional; see criteria)
|
||||
│ │ └── specs/ # Delta changes
|
||||
│ │ └── [capability]/
|
||||
│ │ └── spec.md # ADDED/MODIFIED/REMOVED
|
||||
│ └── archive/ # Completed changes
|
||||
```
|
||||
|
||||
## Creating Change Proposals
|
||||
|
||||
### Decision Tree
|
||||
|
||||
```
|
||||
New request?
|
||||
├─ Bug fix restoring spec behavior? → Fix directly
|
||||
├─ Typo/format/comment? → Fix directly
|
||||
├─ New feature/capability? → Create proposal
|
||||
├─ Breaking change? → Create proposal
|
||||
├─ Architecture change? → Create proposal
|
||||
└─ Unclear? → Create proposal (safer)
|
||||
```
|
||||
|
||||
### Proposal Structure
|
||||
|
||||
1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique)
|
||||
|
||||
2. **Write proposal.md:**
|
||||
```markdown
|
||||
# Change: [Brief description of change]
|
||||
|
||||
## Why
|
||||
[1-2 sentences on problem/opportunity]
|
||||
|
||||
## What Changes
|
||||
- [Bullet list of changes]
|
||||
- [Mark breaking changes with **BREAKING**]
|
||||
|
||||
## Impact
|
||||
- Affected specs: [list capabilities]
|
||||
- Affected code: [key files/systems]
|
||||
```
|
||||
|
||||
3. **Create spec deltas:** `specs/[capability]/spec.md`
|
||||
```markdown
|
||||
## ADDED Requirements
|
||||
### Requirement: New Feature
|
||||
The system SHALL provide...
|
||||
|
||||
#### Scenario: Success case
|
||||
- **WHEN** user performs action
|
||||
- **THEN** expected result
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Existing Feature
|
||||
[Complete modified requirement]
|
||||
|
||||
## REMOVED Requirements
|
||||
### Requirement: Old Feature
|
||||
**Reason**: [Why removing]
|
||||
**Migration**: [How to handle]
|
||||
```
|
||||
If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs/<capability>/spec.md`—one per capability.
|
||||
|
||||
4. **Create tasks.md:**
|
||||
```markdown
|
||||
## 1. Implementation
|
||||
- [ ] 1.1 Create database schema
|
||||
- [ ] 1.2 Implement API endpoint
|
||||
- [ ] 1.3 Add frontend component
|
||||
- [ ] 1.4 Write tests
|
||||
```
|
||||
|
||||
5. **Create design.md when needed:**
|
||||
Create `design.md` if any of the following apply; otherwise omit it:
|
||||
- Cross-cutting change (multiple services/modules) or a new architectural pattern
|
||||
- New external dependency or significant data model changes
|
||||
- Security, performance, or migration complexity
|
||||
- Ambiguity that benefits from technical decisions before coding
|
||||
|
||||
Minimal `design.md` skeleton:
|
||||
```markdown
|
||||
## Context
|
||||
[Background, constraints, stakeholders]
|
||||
|
||||
## Goals / Non-Goals
|
||||
- Goals: [...]
|
||||
- Non-Goals: [...]
|
||||
|
||||
## Decisions
|
||||
- Decision: [What and why]
|
||||
- Alternatives considered: [Options + rationale]
|
||||
|
||||
## Risks / Trade-offs
|
||||
- [Risk] → Mitigation
|
||||
|
||||
## Migration Plan
|
||||
[Steps, rollback]
|
||||
|
||||
## Open Questions
|
||||
- [...]
|
||||
```
|
||||
|
||||
## Spec File Format
|
||||
|
||||
### Critical: Scenario Formatting
|
||||
|
||||
**CORRECT** (use #### headers):
|
||||
```markdown
|
||||
#### Scenario: User login success
|
||||
- **WHEN** valid credentials provided
|
||||
- **THEN** return JWT token
|
||||
```
|
||||
|
||||
**WRONG** (don't use bullets or bold):
|
||||
```markdown
|
||||
- **Scenario: User login** ❌
|
||||
**Scenario**: User login ❌
|
||||
### Scenario: User login ❌
|
||||
```
|
||||
|
||||
Every requirement MUST have at least one scenario.
|
||||
|
||||
### Requirement Wording
|
||||
- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative)
|
||||
|
||||
### Delta Operations
|
||||
|
||||
- `## ADDED Requirements` - New capabilities
|
||||
- `## MODIFIED Requirements` - Changed behavior
|
||||
- `## REMOVED Requirements` - Deprecated features
|
||||
- `## RENAMED Requirements` - Name changes
|
||||
|
||||
Headers matched with `trim(header)` - whitespace ignored.
|
||||
|
||||
#### When to use ADDED vs MODIFIED
|
||||
- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement.
|
||||
- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details.
|
||||
- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name.
|
||||
|
||||
Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead.
|
||||
|
||||
Authoring a MODIFIED requirement correctly:
|
||||
1) Locate the existing requirement in `openspec/specs/<capability>/spec.md`.
|
||||
2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios).
|
||||
3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior.
|
||||
4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`.
|
||||
|
||||
Example for RENAMED:
|
||||
```markdown
|
||||
## RENAMED Requirements
|
||||
- FROM: `### Requirement: Login`
|
||||
- TO: `### Requirement: User Authentication`
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Errors
|
||||
|
||||
**"Change must have at least one delta"**
|
||||
- Check `changes/[name]/specs/` exists with .md files
|
||||
- Verify files have operation prefixes (## ADDED Requirements)
|
||||
|
||||
**"Requirement must have at least one scenario"**
|
||||
- Check scenarios use `#### Scenario:` format (4 hashtags)
|
||||
- Don't use bullet points or bold for scenario headers
|
||||
|
||||
**Silent scenario parsing failures**
|
||||
- Exact format required: `#### Scenario: Name`
|
||||
- Debug with: `openspec show [change] --json --deltas-only`
|
||||
|
||||
### Validation Tips
|
||||
|
||||
```bash
|
||||
# Always use strict mode for comprehensive checks
|
||||
openspec validate [change] --strict
|
||||
|
||||
# Debug delta parsing
|
||||
openspec show [change] --json | jq '.deltas'
|
||||
|
||||
# Check specific requirement
|
||||
openspec show [spec] --json -r 1
|
||||
```
|
||||
|
||||
## Happy Path Script
|
||||
|
||||
```bash
|
||||
# 1) Explore current state
|
||||
openspec spec list --long
|
||||
openspec list
|
||||
# Optional full-text search:
|
||||
# rg -n "Requirement:|Scenario:" openspec/specs
|
||||
# rg -n "^#|Requirement:" openspec/changes
|
||||
|
||||
# 2) Choose change id and scaffold
|
||||
CHANGE=add-two-factor-auth
|
||||
mkdir -p openspec/changes/$CHANGE/{specs/auth}
|
||||
printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md
|
||||
printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md
|
||||
|
||||
# 3) Add deltas (example)
|
||||
cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF'
|
||||
## ADDED Requirements
|
||||
### Requirement: Two-Factor Authentication
|
||||
Users MUST provide a second factor during login.
|
||||
|
||||
#### Scenario: OTP required
|
||||
- **WHEN** valid credentials are provided
|
||||
- **THEN** an OTP challenge is required
|
||||
EOF
|
||||
|
||||
# 4) Validate
|
||||
openspec validate $CHANGE --strict
|
||||
```
|
||||
|
||||
## Multi-Capability Example
|
||||
|
||||
```
|
||||
openspec/changes/add-2fa-notify/
|
||||
├── proposal.md
|
||||
├── tasks.md
|
||||
└── specs/
|
||||
├── auth/
|
||||
│ └── spec.md # ADDED: Two-Factor Authentication
|
||||
└── notifications/
|
||||
└── spec.md # ADDED: OTP email notification
|
||||
```
|
||||
|
||||
auth/spec.md
|
||||
```markdown
|
||||
## ADDED Requirements
|
||||
### Requirement: Two-Factor Authentication
|
||||
...
|
||||
```
|
||||
|
||||
notifications/spec.md
|
||||
```markdown
|
||||
## ADDED Requirements
|
||||
### Requirement: OTP Email Notification
|
||||
...
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Simplicity First
|
||||
- Default to <100 lines of new code
|
||||
- Single-file implementations until proven insufficient
|
||||
- Avoid frameworks without clear justification
|
||||
- Choose boring, proven patterns
|
||||
|
||||
### Complexity Triggers
|
||||
Only add complexity with:
|
||||
- Performance data showing current solution too slow
|
||||
- Concrete scale requirements (>1000 users, >100MB data)
|
||||
- Multiple proven use cases requiring abstraction
|
||||
|
||||
### Clear References
|
||||
- Use `file.ts:42` format for code locations
|
||||
- Reference specs as `specs/auth/spec.md`
|
||||
- Link related changes and PRs
|
||||
|
||||
### Capability Naming
|
||||
- Use verb-noun: `user-auth`, `payment-capture`
|
||||
- Single purpose per capability
|
||||
- 10-minute understandability rule
|
||||
- Split if description needs "AND"
|
||||
|
||||
### Change ID Naming
|
||||
- Use kebab-case, short and descriptive: `add-two-factor-auth`
|
||||
- Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-`
|
||||
- Ensure uniqueness; if taken, append `-2`, `-3`, etc.
|
||||
|
||||
## Tool Selection Guide
|
||||
|
||||
| Task | Tool | Why |
|
||||
|------|------|-----|
|
||||
| Find files by pattern | Glob | Fast pattern matching |
|
||||
| Search code content | Grep | Optimized regex search |
|
||||
| Read specific files | Read | Direct file access |
|
||||
| Explore unknown scope | Task | Multi-step investigation |
|
||||
|
||||
## Error Recovery
|
||||
|
||||
### Change Conflicts
|
||||
1. Run `openspec list` to see active changes
|
||||
2. Check for overlapping specs
|
||||
3. Coordinate with change owners
|
||||
4. Consider combining proposals
|
||||
|
||||
### Validation Failures
|
||||
1. Run with `--strict` flag
|
||||
2. Check JSON output for details
|
||||
3. Verify spec file format
|
||||
4. Ensure scenarios properly formatted
|
||||
|
||||
### Missing Context
|
||||
1. Read project.md first
|
||||
2. Check related specs
|
||||
3. Review recent archives
|
||||
4. Ask for clarification
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Stage Indicators
|
||||
- `changes/` - Proposed, not yet built
|
||||
- `specs/` - Built and deployed
|
||||
- `archive/` - Completed changes
|
||||
|
||||
### File Purposes
|
||||
- `proposal.md` - Why and what
|
||||
- `tasks.md` - Implementation steps
|
||||
- `design.md` - Technical decisions
|
||||
- `spec.md` - Requirements and behavior
|
||||
|
||||
### CLI Essentials
|
||||
```bash
|
||||
openspec list # What's in progress?
|
||||
openspec show [item] # View details
|
||||
openspec validate --strict # Is it correct?
|
||||
openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
|
||||
```
|
||||
|
||||
Remember: Specs are truth. Changes are proposals. Keep them in sync.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Change: Add single-spacing transformer for markdown
|
||||
|
||||
## Why
|
||||
|
||||
Users may want to collapse multiple consecutive blank lines in pasted content into single blank lines without removing all blank lines entirely. This provides a middle-ground option between preserving all blank lines as-is and removing them completely.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a new markdown transformer that collapses multiple consecutive blank lines (2+) into a single blank line
|
||||
- Add a new setting "Convert to single-spaced" that appears above "Remove empty lines" in the Markdown transformations section
|
||||
- The setting defaults to Off (false) to preserve existing behavior
|
||||
- When "Remove empty lines" is enabled, the "Convert to single-spaced" option is disabled (not applicable) since all blank lines are removed anyway
|
||||
- The transformer is applied in the markdown transformation pipeline before empty line removal
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `markdown-transformations`
|
||||
- Affected code:
|
||||
- `src/markdownTransformer.ts` - Add single-spacing logic
|
||||
- `src/main.ts` - Add `convertToSingleSpaced` setting and UI toggle with conditional disabling
|
||||
- No breaking changes
|
||||
- Backward compatible: defaults to Off, existing behavior unchanged
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# Markdown Transformations
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Single-spacing transformation
|
||||
The system SHALL provide an option to collapse multiple consecutive blank lines into a single blank line in the markdown output.
|
||||
|
||||
#### Scenario: Multiple blank lines collapsed to single
|
||||
- **WHEN** `convertToSingleSpaced` is enabled (true)
|
||||
- **AND** the markdown content contains 2 or more consecutive blank lines
|
||||
- **THEN** the consecutive blank lines SHALL be replaced with exactly 1 blank line
|
||||
- **AND** the `appliedTransformations` flag SHALL be set to true
|
||||
|
||||
#### Scenario: Single blank lines preserved
|
||||
- **WHEN** `convertToSingleSpaced` is enabled (true)
|
||||
- **AND** the markdown content contains single blank lines (not consecutive)
|
||||
- **THEN** those single blank lines SHALL be preserved as-is
|
||||
- **AND** no transformation is applied to those lines
|
||||
|
||||
#### Scenario: Non-blank lines unaffected
|
||||
- **WHEN** `convertToSingleSpaced` is enabled (true)
|
||||
- **THEN** all non-blank lines SHALL remain unchanged
|
||||
|
||||
#### Scenario: Feature disabled preserves original behavior
|
||||
- **WHEN** `convertToSingleSpaced` is disabled (false)
|
||||
- **THEN** all blank lines SHALL remain unchanged regardless of how many are consecutive
|
||||
- **AND** the `appliedTransformations` flag SHALL NOT be set based on spacing
|
||||
|
||||
#### Scenario: Interaction with removeEmptyLines
|
||||
- **WHEN** `removeEmptyLines` is enabled (true)
|
||||
- **THEN** the single-spacing transformer SHALL be skipped (not applied)
|
||||
- **AND** the empty line removal logic SHALL be applied instead
|
||||
|
||||
### Requirement: Single-spacing configuration
|
||||
The system SHALL provide a setting to enable or disable the single-spacing transformation.
|
||||
|
||||
#### Scenario: Setting defaults to disabled
|
||||
- **WHEN** the plugin is initialized with no prior settings
|
||||
- **THEN** the `convertToSingleSpaced` setting SHALL default to false
|
||||
|
||||
#### Scenario: Setting persists across sessions
|
||||
- **WHEN** a user enables or disables the `convertToSingleSpaced` setting
|
||||
- **AND** the settings are saved
|
||||
- **THEN** the setting value SHALL be persisted and restored on plugin reload
|
||||
|
||||
### Requirement: Single-spacing UI control
|
||||
The system SHALL provide a user interface control for the single-spacing transformation setting.
|
||||
|
||||
#### Scenario: Toggle positioned above "Remove empty lines"
|
||||
- **WHEN** the settings UI is displayed
|
||||
- **THEN** the "Convert to single-spaced" toggle SHALL appear in the Markdown transformations section
|
||||
- **AND** it SHALL be positioned above the "Remove empty lines" toggle
|
||||
|
||||
#### Scenario: Toggle description
|
||||
- **WHEN** the "Convert to single-spaced" toggle is displayed
|
||||
- **THEN** the description SHALL read "Collapse multiple consecutive blank lines into a single blank line"
|
||||
|
||||
#### Scenario: Toggle disabled when removeEmptyLines is enabled
|
||||
- **WHEN** the "Remove empty lines" setting is enabled (true)
|
||||
- **THEN** the "Convert to single-spaced" toggle SHALL be disabled (non-interactive)
|
||||
- **AND** it SHALL be visually indicated as disabled (grayed out or similar)
|
||||
|
||||
#### Scenario: Toggle enabled when removeEmptyLines is disabled
|
||||
- **WHEN** the "Remove empty lines" setting is disabled (false)
|
||||
- **THEN** the "Convert to single-spaced" toggle SHALL be enabled (interactive)
|
||||
- **AND** users SHALL be able to toggle it on or off
|
||||
|
||||
#### Scenario: UI updates when removeEmptyLines changes
|
||||
- **WHEN** the "Remove empty lines" setting is toggled
|
||||
- **THEN** the disabled/enabled state of the "Convert to single-spaced" toggle SHALL update accordingly
|
||||
|
||||
### Requirement: Transform pipeline order
|
||||
The system SHALL apply transformations in a defined order to ensure consistent behavior.
|
||||
|
||||
#### Scenario: Single-spacing before empty line removal
|
||||
- **WHEN** both `convertToSingleSpaced` and `removeEmptyLines` are configured (regardless of values)
|
||||
- **THEN** the single-spacing transformation logic SHALL be evaluated before the empty line removal logic in the code
|
||||
- **AND** if `removeEmptyLines` is true, single-spacing SHALL be skipped as an optimization
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
# Implementation Tasks
|
||||
|
||||
## 1. Update settings interface and defaults
|
||||
- [x] 1.1 Add `convertToSingleSpaced: boolean` property to `PasteReformmatterSettings` interface in `src/main.ts`
|
||||
- [x] 1.2 Add `convertToSingleSpaced: false` to `DEFAULT_SETTINGS` in `src/main.ts`
|
||||
|
||||
## 2. Implement single-spacing transformer
|
||||
- [x] 2.1 Add `convertToSingleSpaced` parameter to `transformMarkdown` function signature in `src/markdownTransformer.ts`
|
||||
- [x] 2.2 Implement single-spacing logic that collapses 2+ consecutive blank lines into 1 blank line
|
||||
- [x] 2.3 Apply transformer before the `removeEmptyLines` logic in the transformation pipeline
|
||||
- [x] 2.4 Skip single-spacing transformer if `removeEmptyLines` is enabled (optimization - no point processing if lines will be removed)
|
||||
- [x] 2.5 Set `appliedTransformations` flag when single-spacing makes changes
|
||||
|
||||
## 3. Add settings UI
|
||||
- [x] 3.1 Add "Convert to single-spaced" toggle in settings UI, positioned above "Remove empty lines" setting
|
||||
- [x] 3.2 Set description text: "Collapse multiple consecutive blank lines into a single blank line"
|
||||
- [x] 3.3 Implement conditional disabling: when `removeEmptyLines` is true, disable the toggle and show it as disabled/grayed
|
||||
- [x] 3.4 Update onChange handler to save settings
|
||||
- [x] 3.5 When `removeEmptyLines` changes, refresh display to update disabled state of "Convert to single-spaced" toggle
|
||||
|
||||
## 4. Integration
|
||||
- [x] 4.1 Pass `convertToSingleSpaced` setting to `transformMarkdown` call in `src/main.ts:doPaste()`
|
||||
- [x] 4.2 Verify backward compatibility with existing settings (defaults to false)
|
||||
|
||||
## 5. Testing
|
||||
- [x] 5.1 Build project: `npm run build`
|
||||
- [x] 5.2 Test single-spacing with text containing 2+ consecutive blank lines
|
||||
- [x] 5.3 Test that single-spacing is skipped when "Remove empty lines" is enabled
|
||||
- [x] 5.4 Test UI toggle behavior and disabled state interaction
|
||||
- [x] 5.5 Test that existing behavior is preserved when feature is disabled (default)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# Change: Skip reformatting when pasting into a code block
|
||||
|
||||
## Why
|
||||
When a user pastes content into a fenced code block (`` ``` ``), the plugin reformats the pasted text (HTML-to-Markdown conversion, heading cascade, regex replacements, etc.). This destroys the raw content the user intended to paste as code. The plugin should detect code block context and let Obsidian's default paste proceed. (GitHub issue #14)
|
||||
|
||||
## What Changes
|
||||
- Add a code block detection function that scans backward from the cursor to determine if the cursor is inside a fenced code block
|
||||
- Add an early-return guard in `onPaste()` that skips reformatting when the cursor is inside a code block
|
||||
|
||||
## Impact
|
||||
- Affected specs: `paste-interception`
|
||||
- Affected code: `src/main.ts` (`onPaste` method, new helper function)
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
## ADDED Requirements
|
||||
### Requirement: Code block paste bypass
|
||||
The plugin SHALL NOT reformat pasted content when the cursor is inside a fenced code block. When a fenced code block is detected, the plugin SHALL allow Obsidian's default paste behavior to proceed unmodified.
|
||||
|
||||
A fenced code block is detected by scanning backward from the cursor line to find an opening fence (a line starting with `` ``` `` or `~~~`, optionally followed by a language identifier) that has no corresponding closing fence before the cursor position.
|
||||
|
||||
#### Scenario: Paste inside an open fenced code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is positioned inside a fenced code block (between an opening `` ``` `` and before a closing `` ``` ``)
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL skip all transformations
|
||||
- **AND** `event.preventDefault()` SHALL NOT be called
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
#### Scenario: Paste outside a code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is NOT inside a fenced code block
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL process the paste as normal (applying configured transformations)
|
||||
|
||||
#### Scenario: Paste after a closed code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the document contains a fenced code block that has both opening and closing fences
|
||||
- **AND** the cursor is positioned after the closing fence
|
||||
- **THEN** the plugin SHALL process the paste as normal (the code block is closed, cursor is not inside it)
|
||||
|
||||
#### Scenario: Fenced code block with language specifier
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is inside a fenced code block that has a language specifier (e.g., `` ```javascript ``)
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL skip all transformations
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
#### Scenario: Tilde-fenced code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is inside a tilde-fenced code block (opened with `~~~`)
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL skip all transformations
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
## 1. Implementation
|
||||
- [x] 1.1 Add `isCursorInCodeBlock(editor)` helper to `src/main.ts` that scans lines backward from the cursor to detect unclosed fenced code blocks (triple-backtick or triple-tilde)
|
||||
- [x] 1.2 Add early-return guard in `onPaste()` that calls `isCursorInCodeBlock()` and skips reformatting when inside a code block
|
||||
- [x] 1.3 Add debug log message when paste is skipped due to code block context
|
||||
|
||||
## 2. Verification
|
||||
- [x] 2.1 Build with `npm run build` to confirm no compilation errors
|
||||
- [ ] 2.2 Manual test: paste HTML content into a fenced code block and verify raw text is pasted (no reformatting)
|
||||
- [ ] 2.3 Manual test: paste HTML content outside a code block and verify reformatting still works
|
||||
- [ ] 2.4 Manual test: paste into a code block with a language specifier (e.g., ```js) and verify raw text is pasted
|
||||
- [ ] 2.5 Manual test: paste into an indented code block (4 spaces) -- note: this change only targets fenced blocks
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Change: Fix heading cascade appliedTransformations flag overwrite
|
||||
|
||||
Fixes: GitHub Issue #16 - "Text is pasted twice"
|
||||
|
||||
## Why
|
||||
|
||||
When pasting content containing headings via default paste (Ctrl+V / context menu) with "Contextual cascade" enabled, the `appliedTransformations` flag in the heading cascade logic is overwritten on each heading iteration instead of being accumulated. If the last heading processed does not require a level change, the flag is reset to `false`, causing `doPaste()` to return `false`. This means `event.preventDefault()` is never called, so Obsidian's default paste fires on content that the plugin already transformed and inserted -- or, more commonly, the plugin fails to insert its transformed content at all and the user gets untransformed output when they expected heading adjustments.
|
||||
|
||||
The same bug exists in both the contextual cascade path and the standard `maxHeadingLevel` cascade path.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Fix `appliedTransformations` assignment in `src/markdownTransformer.ts` line 81 (contextual cascade) to accumulate instead of overwrite
|
||||
- Fix `appliedTransformations` assignment in `src/markdownTransformer.ts` line 112 (maxHeadingLevel cascade) to accumulate instead of overwrite
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `paste-interception` (new -- no existing spec covers this behavior)
|
||||
- Affected code: `src/markdownTransformer.ts` (lines 81, 112)
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Heading cascade transformation flag accumulation
|
||||
The heading cascade logic in `transformMarkdown` SHALL accumulate the `appliedTransformations` flag across all headings processed, so that if any heading's level is changed, the flag remains `true` for the entire paste operation.
|
||||
|
||||
#### Scenario: Multiple headings where only the first is changed
|
||||
- **WHEN** contextual cascade is enabled
|
||||
- **AND** the cursor is under an H2 heading
|
||||
- **AND** the pasted content contains `# Title` followed by `### Detail`
|
||||
- **THEN** the `appliedTransformations` flag SHALL be `true` after processing all headings
|
||||
- **AND** `doPaste()` SHALL return `true`
|
||||
- **AND** `event.preventDefault()` SHALL be called to suppress Obsidian's default paste
|
||||
|
||||
#### Scenario: Multiple headings where the last does not need adjustment
|
||||
- **WHEN** contextual cascade is enabled
|
||||
- **AND** the cursor is under an H2 heading
|
||||
- **AND** the pasted content ends with a heading that already matches the target level
|
||||
- **THEN** the `appliedTransformations` flag SHALL remain `true` from earlier heading changes
|
||||
- **AND** the content SHALL be pasted exactly once with corrected heading levels
|
||||
|
||||
#### Scenario: No headings need adjustment
|
||||
- **WHEN** contextual cascade is enabled
|
||||
- **AND** no headings in the pasted content require level changes
|
||||
- **THEN** the `appliedTransformations` flag SHALL be `false`
|
||||
- **AND** `doPaste()` SHALL return `false` (unless other transformations applied)
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
#### Scenario: maxHeadingLevel cascade flag accumulation
|
||||
- **WHEN** contextual cascade is disabled
|
||||
- **AND** `maxHeadingLevel` is set above 1
|
||||
- **AND** `cascadeHeadingLevels` is enabled
|
||||
- **AND** pasted content contains multiple headings where at least one is changed
|
||||
- **THEN** the `appliedTransformations` flag SHALL remain `true` after processing all headings
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
## 1. Fix appliedTransformations flag
|
||||
|
||||
- [x] 1.1 In `src/markdownTransformer.ts` line 81, change `appliedTransformations = (newLevel !== currentLevel)` to `appliedTransformations = appliedTransformations || (newLevel !== currentLevel)`
|
||||
- [x] 1.2 In `src/markdownTransformer.ts` line 112, apply the same accumulation fix
|
||||
|
||||
## 2. Validation
|
||||
|
||||
- [x] 2.1 Run `npm run build` to verify TypeScript compilation
|
||||
- [ ] 2.2 Manual test: paste content with headings (e.g. `# Title\n## Section`) with contextual cascade enabled under an existing heading -- verify single paste with correct heading levels
|
||||
- [ ] 2.3 Manual test: paste same content via Command Palette "Reformat and Paste" -- verify unchanged behavior
|
||||
- [ ] 2.4 Manual test: paste content with headings using maxHeadingLevel cascade (contextual cascade off) -- verify single paste with correct heading levels
|
||||
- [ ] 2.5 Manual test: paste content with no headings -- verify default paste behavior is unaffected
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
# Project Context
|
||||
|
||||
## Purpose
|
||||
|
||||
Paste Reformatter is an Obsidian plugin that gives users precise control over how pasted HTML and plain text content is transformed when pasted into notes. The plugin automatically processes clipboard content to:
|
||||
|
||||
- Transform HTML content before converting to Markdown for better formatting results
|
||||
- Apply customizable regex transformations to both HTML and Markdown
|
||||
- Automatically adjust heading levels to match document context
|
||||
- Clean up unwanted formatting (empty elements, hard line breaks, blank lines)
|
||||
- Escape markdown syntax when needed
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **TypeScript 4.7.4** - Primary language (ES6 target)
|
||||
- **Obsidian API** - Plugin framework and editor integration
|
||||
- **esbuild 0.17.3** - Build tool and bundler
|
||||
- **DOM APIs** - HTML parsing and manipulation
|
||||
- **Obsidian's htmlToMarkdown** - Built-in HTML to Markdown conversion
|
||||
|
||||
## Project Conventions
|
||||
|
||||
### Code Style
|
||||
|
||||
- TypeScript with strict null checks enabled
|
||||
- ES6+ syntax with ESNext module format
|
||||
- 4-space tabs for indentation
|
||||
- Camel case for variables and functions, PascalCase for classes/interfaces
|
||||
- Comprehensive JSDoc comments for public functions
|
||||
- Descriptive variable names (e.g., `appliedTransformations`, `contextLevel`)
|
||||
|
||||
### Architecture Patterns
|
||||
|
||||
The codebase follows a modular architecture with clear separation of concerns:
|
||||
|
||||
**Core Plugin Structure (`src/main.ts`)**:
|
||||
- `PasteReformatter` class extends Obsidian's `Plugin` class
|
||||
- Registers clipboard event handlers and commands
|
||||
- Manages settings persistence via `loadData()`/`saveData()`
|
||||
- Delegates transformation logic to specialized modules
|
||||
|
||||
**HTML Transformation Module (`src/htmlTransformer.ts`)**:
|
||||
- Pure function: `transformHTML(html, settings) => {html, appliedTransformations}`
|
||||
- Uses DOMParser for safe HTML manipulation (never uses innerHTML)
|
||||
- Applies transformations in sequence: regex → line breaks → empty elements
|
||||
- Returns transformation status to determine if paste should be intercepted
|
||||
|
||||
**Markdown Transformation Module (`src/markdownTransformer.ts`)**:
|
||||
- Pure function: `transformMarkdown(markdown, settings, contextLevel, escapeMarkdown) => {markdown, appliedTransformations}`
|
||||
- Handles heading level adjustments with three modes:
|
||||
- Contextual cascade (based on cursor position)
|
||||
- Standard cascade (based on max heading level)
|
||||
- Simple capping (no cascade)
|
||||
- Applies regex replacements and empty line removal
|
||||
- Supports markdown escaping for special paste mode
|
||||
|
||||
**Settings Management**:
|
||||
- `PasteReformmatterSettings` interface defines all configuration
|
||||
- `DEFAULT_SETTINGS` constant for initialization
|
||||
- Custom settings tab with rich UI (tables, icons, accessibility features)
|
||||
- Settings UI built entirely with DOM APIs (no innerHTML)
|
||||
|
||||
**Key Design Patterns**:
|
||||
- **Transformation Pipeline**: HTML → Markdown → Final transformations
|
||||
- **Non-invasive Override**: Only prevents default paste if transformations were applied
|
||||
- **Safe DOM Manipulation**: Exclusively uses createElement/appendChild patterns
|
||||
- **Accessibility First**: All UI elements include ARIA labels and keyboard support
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
Currently, the project does not have automated tests. Testing is performed manually:
|
||||
|
||||
- Use `npm run build` to verify TypeScript compilation
|
||||
- Manual testing in Obsidian vault with various paste scenarios
|
||||
- Test different content sources (web pages, Google Docs, Word, plain text)
|
||||
- Verify heading cascade behavior with cascade-rules.md examples
|
||||
- Check settings UI behavior and persistence
|
||||
|
||||
When adding tests, prioritize:
|
||||
1. Transformation logic (htmlTransformer, markdownTransformer)
|
||||
2. Heading cascade algorithms (all three modes)
|
||||
3. Regex replacement handling and error cases
|
||||
4. Settings persistence and migration
|
||||
|
||||
### Git Workflow
|
||||
|
||||
- **Main branch**: Production-ready code
|
||||
- **Commit style**: Conventional commits format
|
||||
- `feat:` - New features
|
||||
- `fix:` - Bug fixes
|
||||
- `refactor:` - Code improvements without behavior change
|
||||
- `chore:` - Maintenance tasks (version bumps, dependencies)
|
||||
- `docs:` - Documentation updates
|
||||
- **Version bumping**: Automated via `version-bump.mjs` script
|
||||
- **Release process**: GitHub Actions workflow (`.github/workflows/release.yml`)
|
||||
|
||||
## Domain Context
|
||||
|
||||
### Obsidian Plugin Ecosystem
|
||||
|
||||
- Plugins extend Obsidian's markdown editor functionality
|
||||
- Must work across desktop and mobile (this plugin works on both)
|
||||
- Need to respect user themes and CSS variables
|
||||
- Should integrate cleanly with other plugins (non-invasive event handling)
|
||||
|
||||
### Clipboard Data Handling
|
||||
|
||||
The plugin processes clipboard data in this order of priority:
|
||||
1. **HTML format** (`text/html`) - Preferred for rich content from web/apps
|
||||
2. **Plain text** (`text/plain`) - Treated as already being Markdown
|
||||
|
||||
### Heading Cascade Logic
|
||||
|
||||
Three distinct modes with different behaviors (see `cascade-rules.md`):
|
||||
|
||||
1. **No Cascade** (maxHeadingLevel=1 OR cascadeHeadingLevels=false):
|
||||
- Simple capping: H1→H3, H2→H3 if max is H3
|
||||
|
||||
2. **Standard Cascade** (cascadeHeadingLevels=true, contextualCascade=false):
|
||||
- Preserves hierarchy: H1→H3, H2→H4, H3→H5 if max is H3
|
||||
- Caps at H6
|
||||
|
||||
3. **Contextual Cascade** (contextualCascade=true):
|
||||
- Overrides other settings
|
||||
- Adjusts based on cursor's current heading section
|
||||
- In H2 section: H1→H3, H2→H4, H3→H5, etc.
|
||||
|
||||
### Special Markers
|
||||
|
||||
- `data-preserve="true"` attribute marks line breaks to preserve during empty element removal
|
||||
- Zero-width space (`\u200B`) used as placeholder to prevent paragraph collapse
|
||||
|
||||
## Important Constraints
|
||||
|
||||
### Security
|
||||
|
||||
- **Never use innerHTML or outerHTML** - XSS vulnerability risk
|
||||
- Always use DOMParser, createElement, appendChild patterns
|
||||
- Sanitize user-provided regex patterns with try-catch blocks
|
||||
|
||||
### Obsidian API Compatibility
|
||||
|
||||
- Minimum supported version: 0.15.0 (defined in manifest.json)
|
||||
- Must use current (non-deprecated) API methods
|
||||
- Use `app.workspace.getActiveViewOfType(MarkdownView)` not deprecated alternatives
|
||||
- Register events with `registerEvent()` for automatic cleanup
|
||||
|
||||
### Performance
|
||||
|
||||
- Transformations must be fast (paste should feel instant)
|
||||
- Regex replacements loop through array - keep replacement lists reasonable
|
||||
- DOM parsing happens on every paste when enabled - keep HTML clean
|
||||
|
||||
### Browser Compatibility
|
||||
|
||||
- Must work in Electron (Chromium-based)
|
||||
- Uses modern APIs: DOMParser, XMLSerializer, navigator.clipboard
|
||||
- Targets ES2018 in build output
|
||||
|
||||
## External Dependencies
|
||||
|
||||
### Runtime Dependencies
|
||||
|
||||
- **Obsidian API** (`obsidian` module) - Provides:
|
||||
- `Plugin` base class
|
||||
- `MarkdownView` for editor access
|
||||
- `Setting` for settings UI
|
||||
- `htmlToMarkdown()` conversion function
|
||||
- `Notice` for user notifications
|
||||
- `setIcon()` for UI icons
|
||||
|
||||
### Development Dependencies
|
||||
|
||||
- **esbuild** - Fast TypeScript bundler
|
||||
- **TypeScript** - Type checking and compilation
|
||||
- **@types/node** - Node.js type definitions
|
||||
- **tslib** - TypeScript runtime helpers
|
||||
- **builtin-modules** - List of Node.js built-in modules to exclude from bundle
|
||||
|
||||
### Build Process
|
||||
|
||||
1. TypeScript compilation check (`tsc -noEmit -skipLibCheck`)
|
||||
2. esbuild bundles `src/main.ts` → `dist/main.js`
|
||||
3. Styles copied from `styles.css` to dist
|
||||
4. External dependencies (obsidian, electron, codemirror) marked as external
|
||||
5. Production builds are minified, dev builds include inline sourcemaps
|
||||
|
||||
### Distribution
|
||||
|
||||
- Main files: `dist/main.js`, `manifest.json`, `styles.css`
|
||||
- Distributed via Obsidian Community Plugins marketplace
|
||||
- GitHub releases include ZIP with required files
|
||||
- Version managed in: `manifest.json`, `package.json`, `versions.json`
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
# markdown-transformations Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-single-spacing-transformer. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Single-spacing transformation
|
||||
The system SHALL provide an option to collapse multiple consecutive blank lines into a single blank line in the markdown output.
|
||||
|
||||
#### Scenario: Multiple blank lines collapsed to single
|
||||
- **WHEN** `convertToSingleSpaced` is enabled (true)
|
||||
- **AND** the markdown content contains 2 or more consecutive blank lines
|
||||
- **THEN** the consecutive blank lines SHALL be replaced with exactly 1 blank line
|
||||
- **AND** the `appliedTransformations` flag SHALL be set to true
|
||||
|
||||
#### Scenario: Single blank lines preserved
|
||||
- **WHEN** `convertToSingleSpaced` is enabled (true)
|
||||
- **AND** the markdown content contains single blank lines (not consecutive)
|
||||
- **THEN** those single blank lines SHALL be preserved as-is
|
||||
- **AND** no transformation is applied to those lines
|
||||
|
||||
#### Scenario: Non-blank lines unaffected
|
||||
- **WHEN** `convertToSingleSpaced` is enabled (true)
|
||||
- **THEN** all non-blank lines SHALL remain unchanged
|
||||
|
||||
#### Scenario: Feature disabled preserves original behavior
|
||||
- **WHEN** `convertToSingleSpaced` is disabled (false)
|
||||
- **THEN** all blank lines SHALL remain unchanged regardless of how many are consecutive
|
||||
- **AND** the `appliedTransformations` flag SHALL NOT be set based on spacing
|
||||
|
||||
#### Scenario: Interaction with removeEmptyLines
|
||||
- **WHEN** `removeEmptyLines` is enabled (true)
|
||||
- **THEN** the single-spacing transformer SHALL be skipped (not applied)
|
||||
- **AND** the empty line removal logic SHALL be applied instead
|
||||
|
||||
### Requirement: Single-spacing configuration
|
||||
The system SHALL provide a setting to enable or disable the single-spacing transformation.
|
||||
|
||||
#### Scenario: Setting defaults to disabled
|
||||
- **WHEN** the plugin is initialized with no prior settings
|
||||
- **THEN** the `convertToSingleSpaced` setting SHALL default to false
|
||||
|
||||
#### Scenario: Setting persists across sessions
|
||||
- **WHEN** a user enables or disables the `convertToSingleSpaced` setting
|
||||
- **AND** the settings are saved
|
||||
- **THEN** the setting value SHALL be persisted and restored on plugin reload
|
||||
|
||||
### Requirement: Single-spacing UI control
|
||||
The system SHALL provide a user interface control for the single-spacing transformation setting.
|
||||
|
||||
#### Scenario: Toggle positioned above "Remove empty lines"
|
||||
- **WHEN** the settings UI is displayed
|
||||
- **THEN** the "Convert to single-spaced" toggle SHALL appear in the Markdown transformations section
|
||||
- **AND** it SHALL be positioned above the "Remove empty lines" toggle
|
||||
|
||||
#### Scenario: Toggle description
|
||||
- **WHEN** the "Convert to single-spaced" toggle is displayed
|
||||
- **THEN** the description SHALL read "Collapse multiple consecutive blank lines into a single blank line"
|
||||
|
||||
#### Scenario: Toggle disabled when removeEmptyLines is enabled
|
||||
- **WHEN** the "Remove empty lines" setting is enabled (true)
|
||||
- **THEN** the "Convert to single-spaced" toggle SHALL be disabled (non-interactive)
|
||||
- **AND** it SHALL be visually indicated as disabled (grayed out or similar)
|
||||
|
||||
#### Scenario: Toggle enabled when removeEmptyLines is disabled
|
||||
- **WHEN** the "Remove empty lines" setting is disabled (false)
|
||||
- **THEN** the "Convert to single-spaced" toggle SHALL be enabled (interactive)
|
||||
- **AND** users SHALL be able to toggle it on or off
|
||||
|
||||
#### Scenario: UI updates when removeEmptyLines changes
|
||||
- **WHEN** the "Remove empty lines" setting is toggled
|
||||
- **THEN** the disabled/enabled state of the "Convert to single-spaced" toggle SHALL update accordingly
|
||||
|
||||
### Requirement: Transform pipeline order
|
||||
The system SHALL apply transformations in a defined order to ensure consistent behavior.
|
||||
|
||||
#### Scenario: Single-spacing before empty line removal
|
||||
- **WHEN** both `convertToSingleSpaced` and `removeEmptyLines` are configured (regardless of values)
|
||||
- **THEN** the single-spacing transformation logic SHALL be evaluated before the empty line removal logic in the code
|
||||
- **AND** if `removeEmptyLines` is true, single-spacing SHALL be skipped as an optimization
|
||||
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
# paste-interception Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change fix-heading-cascade-flag. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Heading cascade transformation flag accumulation
|
||||
The heading cascade logic in `transformMarkdown` SHALL accumulate the `appliedTransformations` flag across all headings processed, so that if any heading's level is changed, the flag remains `true` for the entire paste operation.
|
||||
|
||||
#### Scenario: Multiple headings where only the first is changed
|
||||
- **WHEN** contextual cascade is enabled
|
||||
- **AND** the cursor is under an H2 heading
|
||||
- **AND** the pasted content contains `# Title` followed by `### Detail`
|
||||
- **THEN** the `appliedTransformations` flag SHALL be `true` after processing all headings
|
||||
- **AND** `doPaste()` SHALL return `true`
|
||||
- **AND** `event.preventDefault()` SHALL be called to suppress Obsidian's default paste
|
||||
|
||||
#### Scenario: Multiple headings where the last does not need adjustment
|
||||
- **WHEN** contextual cascade is enabled
|
||||
- **AND** the cursor is under an H2 heading
|
||||
- **AND** the pasted content ends with a heading that already matches the target level
|
||||
- **THEN** the `appliedTransformations` flag SHALL remain `true` from earlier heading changes
|
||||
- **AND** the content SHALL be pasted exactly once with corrected heading levels
|
||||
|
||||
#### Scenario: No headings need adjustment
|
||||
- **WHEN** contextual cascade is enabled
|
||||
- **AND** no headings in the pasted content require level changes
|
||||
- **THEN** the `appliedTransformations` flag SHALL be `false`
|
||||
- **AND** `doPaste()` SHALL return `false` (unless other transformations applied)
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
#### Scenario: maxHeadingLevel cascade flag accumulation
|
||||
- **WHEN** contextual cascade is disabled
|
||||
- **AND** `maxHeadingLevel` is set above 1
|
||||
- **AND** `cascadeHeadingLevels` is enabled
|
||||
- **AND** pasted content contains multiple headings where at least one is changed
|
||||
- **THEN** the `appliedTransformations` flag SHALL remain `true` after processing all headings
|
||||
|
||||
### Requirement: Code block paste bypass
|
||||
The plugin SHALL NOT reformat pasted content when the cursor is inside a fenced code block. When a fenced code block is detected, the plugin SHALL allow Obsidian's default paste behavior to proceed unmodified.
|
||||
|
||||
A fenced code block is detected by scanning backward from the cursor line to find an opening fence (a line starting with `` ``` `` or `~~~`, optionally followed by a language identifier) that has no corresponding closing fence before the cursor position.
|
||||
|
||||
#### Scenario: Paste inside an open fenced code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is positioned inside a fenced code block (between an opening `` ``` `` and before a closing `` ``` ``)
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL skip all transformations
|
||||
- **AND** `event.preventDefault()` SHALL NOT be called
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
#### Scenario: Paste outside a code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is NOT inside a fenced code block
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL process the paste as normal (applying configured transformations)
|
||||
|
||||
#### Scenario: Paste after a closed code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the document contains a fenced code block that has both opening and closing fences
|
||||
- **AND** the cursor is positioned after the closing fence
|
||||
- **THEN** the plugin SHALL process the paste as normal (the code block is closed, cursor is not inside it)
|
||||
|
||||
#### Scenario: Fenced code block with language specifier
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is inside a fenced code block that has a language specifier (e.g., `` ```javascript ``)
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL skip all transformations
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
#### Scenario: Tilde-fenced code block
|
||||
- **WHEN** `pasteOverride` is enabled
|
||||
- **AND** the cursor is inside a tilde-fenced code block (opened with `~~~`)
|
||||
- **AND** the user pastes content
|
||||
- **THEN** the plugin SHALL skip all transformations
|
||||
- **AND** Obsidian's default paste behavior SHALL proceed
|
||||
|
||||
Loading…
Reference in a new issue