feat: Add linked scene/chapter templates with planning-draft file references
- Updated Plot scene template with YAML frontmatter for metadata tracking - Updated First Draft scene template with YAML frontmatter - Added _template.md files in both Plot and First Draft folders - Added create_scene.py script for automatic file creation and linking - Planning files reference draft files via 'draft_file' field - Draft files reference planning files via 'planning_template' field - Word count targets tracked in planning, current count in draft - Added comprehensive scripts/README.md with usage guide - Supports --from-planning and --from-draft for creating missing counterparts Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
id: scene_new
|
||||||
|
title: "New Scene Title"
|
||||||
|
slug: new-scene-title
|
||||||
|
act: 1
|
||||||
|
chapter: 1
|
||||||
|
scene: 99
|
||||||
|
type: scene
|
||||||
|
status: planning
|
||||||
|
word_count_target: 2000
|
||||||
|
word_count_min: 1800
|
||||||
|
word_count_max: 2200
|
||||||
|
pov: ""
|
||||||
|
characters: []
|
||||||
|
setting: ""
|
||||||
|
date: ""
|
||||||
|
time: ""
|
||||||
|
conflict: ""
|
||||||
|
theme: ""
|
||||||
|
tone: ""
|
||||||
|
draft_file: ""
|
||||||
|
order: 99
|
||||||
|
created: ""
|
||||||
|
updated: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
# New Scene Title
|
||||||
|
|
||||||
|
**Planning Template** | **ID:** scene_new | **Status:** {{ status | capitalizes }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planning Metadata
|
||||||
|
|
||||||
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| **Act** | {{ act }} |
|
||||||
|
| **Chapter** | {{ chapter }} |
|
||||||
|
| **Scene** | {{ scene }} |
|
||||||
|
| **Type** | {{ type | capitalizes }} |
|
||||||
|
| **Order** | {{ order }} |
|
||||||
|
| **Draft File** | {{ draft_file if draft_file else "[Create Draft File](#)" }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Creative Elements
|
||||||
|
|
||||||
|
### POV
|
||||||
|
> {{ pov if pov else "Not specified" }}
|
||||||
|
|
||||||
|
### Characters
|
||||||
|
{% for character in characters %}- [[{{ character }}]]
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
### Setting
|
||||||
|
> {{ setting if setting else "Not specified" }}
|
||||||
|
|
||||||
|
### Date & Time
|
||||||
|
> {{ date if date else "Not specified" }} | {{ time if time else "Not specified" }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Story Structure
|
||||||
|
|
||||||
|
### Conflict
|
||||||
|
> {{ conflict if conflict else "Not specified" }}
|
||||||
|
|
||||||
|
### Theme
|
||||||
|
> {{ theme if theme else "Not specified" }}
|
||||||
|
|
||||||
|
### Tone
|
||||||
|
> {{ tone if tone else "Not specified" }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Word Count Goals
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Target** | {{ word_count_target }} |
|
||||||
|
| **Minimum** | {{ word_count_min }} |
|
||||||
|
| **Maximum** | {{ word_count_max }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Planning
|
||||||
|
|
||||||
|
### The Setup
|
||||||
|
>
|
||||||
|
|
||||||
|
### The Reveal
|
||||||
|
>
|
||||||
|
|
||||||
|
### Things to Note
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status Tracking
|
||||||
|
|
||||||
|
- [ ] Scene outline complete
|
||||||
|
- [ ] Character arcs aligned
|
||||||
|
- [ ] Location details verified
|
||||||
|
- [ ] Conflict established
|
||||||
|
- [ ] Word count target set
|
||||||
|
- [ ] Ready for drafting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Instructions:**
|
||||||
|
1. Copy this file to the appropriate act/chapter folder
|
||||||
|
2. Rename to `scene_XX.md` (e.g., `scene_02.md`)
|
||||||
|
3. Update the YAML frontmatter with scene details
|
||||||
|
4. Set `draft_file` to point to the corresponding draft file
|
||||||
|
5. Create the draft file using the template in `06-First Draft/_template.md`
|
||||||
|
|
||||||
|
**Related:** [Story Bible](../../03-Story-Bible/_index.md) | **Created:** {{ created }} | **Updated:** {{ updated }}
|
||||||
@@ -1,23 +1,111 @@
|
|||||||
# Title
|
---
|
||||||
|
id: scene_01
|
||||||
|
title: "Chapter One: The Beginning"
|
||||||
|
slug: chapter-one-the-beginning
|
||||||
|
act: 1
|
||||||
|
chapter: 1
|
||||||
|
scene: 1
|
||||||
|
type: chapter
|
||||||
|
status: planning
|
||||||
|
word_count_target: 2000
|
||||||
|
word_count_min: 1800
|
||||||
|
word_count_max: 2200
|
||||||
|
pov: ""
|
||||||
|
characters: []
|
||||||
|
setting: ""
|
||||||
|
date: ""
|
||||||
|
time: ""
|
||||||
|
conflict: ""
|
||||||
|
theme: ""
|
||||||
|
tone: ""
|
||||||
|
draft_file: "../../06-First Draft/Act 1/scene01.md"
|
||||||
|
order: 1
|
||||||
|
created: ""
|
||||||
|
updated: ""
|
||||||
|
---
|
||||||
|
|
||||||
# Slug
|
# Chapter One: The Beginning
|
||||||
|
|
||||||
## POV:
|
**Planning Template** | **ID:** scene_01 | **Status:** {{ status | capitalizes }}
|
||||||
|
|
||||||
## Characters:
|
---
|
||||||
|
|
||||||
## Setting:
|
## Planning Metadata
|
||||||
|
|
||||||
## Conflict:
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| **Act** | {{ act }} |
|
||||||
|
| **Chapter** | {{ chapter }} |
|
||||||
|
| **Scene** | {{ scene }} |
|
||||||
|
| **Type** | {{ type | capitalizes }} |
|
||||||
|
| **Order** | {{ order }} |
|
||||||
|
| **Draft File** | [`{{ draft_file | replace('../../', '') }}`]({{ draft_file }}) |
|
||||||
|
|
||||||
## Feeling to convey:
|
---
|
||||||
|
|
||||||
## Word Count Target:
|
## Creative Elements
|
||||||
|
|
||||||
## The Setup:
|
### POV
|
||||||
|
> {{ pov if pov else "Not specified" }}
|
||||||
|
|
||||||
## The Reveal:
|
### Characters
|
||||||
|
{% for character in characters %}- [[{{ character }}]]
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
### Things to Note:
|
### Setting
|
||||||
|
> {{ setting if setting else "Not specified" }}
|
||||||
|
|
||||||
### Rough draft:
|
### Date & Time
|
||||||
|
> {{ date if date else "Not specified" }} | {{ time if time else "Not specified" }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Story Structure
|
||||||
|
|
||||||
|
### Conflict
|
||||||
|
> {{ conflict if conflict else "Not specified" }}
|
||||||
|
|
||||||
|
### Theme
|
||||||
|
> {{ theme if theme else "Not specified" }}
|
||||||
|
|
||||||
|
### Tone
|
||||||
|
> {{ tone if tone else "Not specified" }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Word Count Goals
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Target** | {{ word_count_target }} |
|
||||||
|
| **Minimum** | {{ word_count_min }} |
|
||||||
|
| **Maximum** | {{ word_count_max }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Planning
|
||||||
|
|
||||||
|
### The Setup
|
||||||
|
>
|
||||||
|
|
||||||
|
### The Reveal
|
||||||
|
>
|
||||||
|
|
||||||
|
### Things to Note
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status Tracking
|
||||||
|
|
||||||
|
- [ ] Scene outline complete
|
||||||
|
- [ ] Character arcs aligned
|
||||||
|
- [ ] Location details verified
|
||||||
|
- [ ] Conflict established
|
||||||
|
- [ ] Word count target set
|
||||||
|
- [ ] Ready for drafting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Draft File:** [Open Draft]({{ draft_file }}) | **Created:** {{ created }} | **Updated:** {{ updated }}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
id: draft_scene_01
|
||||||
|
title: "Chapter One: The Beginning"
|
||||||
|
slug: chapter-one-the-beginning
|
||||||
|
planning_template: "../../05-Plot/act_1/scene_01.md"
|
||||||
|
act: 1
|
||||||
|
chapter: 1
|
||||||
|
scene: 1
|
||||||
|
type: draft
|
||||||
|
status: not_started
|
||||||
|
word_count: 0
|
||||||
|
word_count_updated: ""
|
||||||
|
session_word_count: 0
|
||||||
|
last_session: ""
|
||||||
|
total_sessions: 0
|
||||||
|
pov: ""
|
||||||
|
version: 1
|
||||||
|
created: ""
|
||||||
|
updated: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
# Chapter One: The Beginning
|
||||||
|
|
||||||
|
**Draft File** | **ID:** draft_scene_01 | **Status:** {{ status | replace('_', ' ') | capitalizes }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planning Reference
|
||||||
|
|
||||||
|
> **Linked to:** [Planning Template]({{ planning_template }})
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Metadata
|
||||||
|
|
||||||
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| **Act** | {{ act }} |
|
||||||
|
| **Chapter** | {{ chapter }} |
|
||||||
|
| **Scene** | {{ scene }} |
|
||||||
|
| **Type** | {{ type | capitalizes }} |
|
||||||
|
| **POV** | {{ pov if pov else "Not set" }} |
|
||||||
|
| **Version** | {{ version }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Word Count Tracking
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Target** | 2000 (from planning) |
|
||||||
|
| **Current** | {{ word_count }} |
|
||||||
|
| **This Session** | {{ session_word_count }} |
|
||||||
|
| **Last Updated** | {{ word_count_updated if word_count_updated else "Never" }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Draft Content
|
||||||
|
|
||||||
|
### Rough Draft:
|
||||||
|
|
||||||
|
[Write your scene content here...]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Writing Notes
|
||||||
|
|
||||||
|
### Session Log
|
||||||
|
| Date | Words Added | Notes |
|
||||||
|
|------|--------------|-------|
|
||||||
|
| {{ last_session if last_session else "" }} | {{ session_word_count }} | |
|
||||||
|
|
||||||
|
### Revision Notes
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status Checklist
|
||||||
|
|
||||||
|
- [ ] First draft complete
|
||||||
|
- [ ] Word count target met
|
||||||
|
- [ ] POV consistent
|
||||||
|
- [ ] Character voices distinct
|
||||||
|
- [ ] Setting details accurate
|
||||||
|
- [ ] Conflict resolved/advanced
|
||||||
|
- [ ] Ready for revision
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Planning Template:** [Open Planning]({{ planning_template }}) | **Created:** {{ created }} | **Updated:** {{ updated }}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
id: draft_scene_new
|
||||||
|
title: "New Scene Title"
|
||||||
|
slug: new-scene-title
|
||||||
|
planning_template: ""
|
||||||
|
act: 1
|
||||||
|
chapter: 1
|
||||||
|
scene: 99
|
||||||
|
type: draft
|
||||||
|
status: not_started
|
||||||
|
word_count: 0
|
||||||
|
word_count_updated: ""
|
||||||
|
session_word_count: 0
|
||||||
|
last_session: ""
|
||||||
|
total_sessions: 0
|
||||||
|
pov: ""
|
||||||
|
version: 1
|
||||||
|
created: ""
|
||||||
|
updated: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
# New Scene Title
|
||||||
|
|
||||||
|
**Draft File** | **ID:** draft_scene_new | **Status:** {{ status | replace('_', ' ') | capitalizes }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planning Reference
|
||||||
|
|
||||||
|
> **Linked to:** {{ planning_template if planning_template else "[Create Planning Template](#)" }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Metadata
|
||||||
|
|
||||||
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| **Act** | {{ act }} |
|
||||||
|
| **Chapter** | {{ chapter }} |
|
||||||
|
| **Scene** | {{ scene }} |
|
||||||
|
| **Type** | {{ type | capitalizes }} |
|
||||||
|
| **POV** | {{ pov if pov else "Not set" }} |
|
||||||
|
| **Version** | {{ version }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Word Count Tracking
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Target** | (from planning) |
|
||||||
|
| **Current** | {{ word_count }} |
|
||||||
|
| **This Session** | {{ session_word_count }} |
|
||||||
|
| **Last Updated** | {{ word_count_updated if word_count_updated else "Never" }} |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Draft Content
|
||||||
|
|
||||||
|
### Rough Draft:
|
||||||
|
|
||||||
|
[Write your scene content here...]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Writing Notes
|
||||||
|
|
||||||
|
### Session Log
|
||||||
|
| Date | Words Added | Notes |
|
||||||
|
|------|--------------|-------|
|
||||||
|
| {{ last_session if last_session else "" }} | {{ session_word_count }} | |
|
||||||
|
|
||||||
|
### Revision Notes
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status Checklist
|
||||||
|
|
||||||
|
- [ ] First draft complete
|
||||||
|
- [ ] Word count target met
|
||||||
|
- [ ] POV consistent
|
||||||
|
- [ ] Character voices distinct
|
||||||
|
- [ ] Setting details accurate
|
||||||
|
- [ ] Conflict resolved/advanced
|
||||||
|
- [ ] Ready for revision
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Instructions:**
|
||||||
|
1. Copy this file to the appropriate Act/Chapter folder
|
||||||
|
2. Rename to `sceneXX.md` (e.g., `scene02.md`)
|
||||||
|
3. Update the YAML frontmatter with scene details
|
||||||
|
4. Set `planning_template` to point to the corresponding planning file
|
||||||
|
5. Create the planning file using the template in `05-Plot/_template.md`
|
||||||
|
6. Update word count as you write
|
||||||
|
|
||||||
|
**Related:** [Story Bible](../../03-Story-Bible/_index.md) | **Created:** {{ created }} | **Updated:** {{ updated }}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
# Scene Management Scripts
|
||||||
|
|
||||||
|
Automate the creation and linking of planning and draft files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scripts Available
|
||||||
|
|
||||||
|
### `create_scene.py`
|
||||||
|
|
||||||
|
Creates linked planning and draft files for new scenes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Create a New Scene Pair
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to your project root
|
||||||
|
cd /path/to/your/project
|
||||||
|
|
||||||
|
# Create a new scene (act 1, chapter 1, scene 2)
|
||||||
|
python scripts/create_scene.py --act 1 --chapter 1 --scene 2 --title "Chapter Two: The Journey"
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
- `05-Plot/act_1/scene_02.md` (planning template)
|
||||||
|
- `06-First Draft/Act 1/scene02.md` (draft file)
|
||||||
|
- Both files are automatically linked to each other
|
||||||
|
|
||||||
|
### 2. Create Draft from Existing Planning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If you have an existing planning file but no draft
|
||||||
|
python scripts/create_scene.py --from-planning 05-Plot/act_1/scene_01.md
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates the corresponding draft file and links it.
|
||||||
|
|
||||||
|
### 3. Create Planning from Existing Draft
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If you have an existing draft file but no planning
|
||||||
|
python scripts/create_scene.py --from-draft "06-First Draft/Act 1/scene01.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates the corresponding planning file and links it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Minimal (auto-generates title)
|
||||||
|
```bash
|
||||||
|
python scripts/create_scene.py --scene 3
|
||||||
|
```
|
||||||
|
Creates scene 3 in act 1, chapter 1 with title "Chapter 1: Scene 3"
|
||||||
|
|
||||||
|
### Full specification
|
||||||
|
```bash
|
||||||
|
python scripts/create_scene.py \
|
||||||
|
--act 2 \
|
||||||
|
--chapter 3 \
|
||||||
|
--scene 5 \
|
||||||
|
--title "Chapter Three: The Revelation" \
|
||||||
|
--type chapter
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch create multiple scenes
|
||||||
|
```bash
|
||||||
|
for scene in {2..10}; do
|
||||||
|
python scripts/create_scene.py --chapter 1 --scene $scene
|
||||||
|
Done
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Planning Files (`05-Plot/`)
|
||||||
|
```
|
||||||
|
05-Plot/
|
||||||
|
├── _template.md # Template for new planning files
|
||||||
|
├── act_1/
|
||||||
|
│ ├── scene_01.md # Planning for act 1, scene 1
|
||||||
|
│ ├── scene_02.md # Planning for act 1, scene 2
|
||||||
|
│ └── ...
|
||||||
|
├── act_2/
|
||||||
|
│ ├── scene_01.md
|
||||||
|
│ └── ...
|
||||||
|
└── act_3/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Each planning file contains:
|
||||||
|
- YAML frontmatter with metadata (act, chapter, scene, word count targets, etc.)
|
||||||
|
- `draft_file` field pointing to the corresponding draft file
|
||||||
|
- Scene planning sections (setup, reveal, notes)
|
||||||
|
|
||||||
|
### Draft Files (`06-First Draft/`)
|
||||||
|
```
|
||||||
|
06-First Draft/
|
||||||
|
├── _template.md # Template for new draft files
|
||||||
|
├── Act 1/
|
||||||
|
│ ├── scene01.md # Draft for act 1, scene 1
|
||||||
|
│ ├── scene02.md # Draft for act 1, scene 2
|
||||||
|
│ └── ...
|
||||||
|
├── Act 2/
|
||||||
|
│ ├── scene01.md
|
||||||
|
│ └── ...
|
||||||
|
└── Act 3/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Each draft file contains:
|
||||||
|
- YAML frontmatter with metadata
|
||||||
|
- `planning_template` field pointing to the corresponding planning file
|
||||||
|
- Word count tracking (current, session, target from planning)
|
||||||
|
- Actual scene content
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Linking System
|
||||||
|
|
||||||
|
The two files are linked via relative paths in their frontmatter:
|
||||||
|
|
||||||
|
**Planning file (`05-Plot/act_1/scene_01.md`):**
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
draft_file: "../../06-First Draft/Act 1/scene01.md"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
**Draft file (`06-First Draft/Act 1/scene01.md`):**
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
planning_template: "../../05-Plot/act_1/scene_01.md"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
This bidirectional linking allows:
|
||||||
|
- Navigating from planning to draft and vice versa
|
||||||
|
- Sharing metadata between files
|
||||||
|
- Tracking progress across both
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Metadata Reference
|
||||||
|
|
||||||
|
### Planning File Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `id` | string | Unique identifier (e.g., `scene_01_01_01`) |
|
||||||
|
| `title` | string | Scene/chapter title |
|
||||||
|
| `slug` | string | URL-friendly identifier |
|
||||||
|
| `act` | int | Act number |
|
||||||
|
| `chapter` | int | Chapter number |
|
||||||
|
| `scene` | int | Scene number |
|
||||||
|
| `type` | string | `scene` \| `chapter` \| `interlude` |
|
||||||
|
| `status` | string | `planning` \| `ready` \| `blocked` |
|
||||||
|
| `word_count_target` | int | Target word count |
|
||||||
|
| `word_count_min` | int | Minimum acceptable words |
|
||||||
|
| `word_count_max` | int | Maximum acceptable words |
|
||||||
|
| `pov` | string | Point of view character |
|
||||||
|
| `characters` | list | Characters in this scene |
|
||||||
|
| `setting` | string | Primary location |
|
||||||
|
| `date` | string | Date in story timeline |
|
||||||
|
| `time` | string | Time of day |
|
||||||
|
| `conflict` | string | Central conflict |
|
||||||
|
| `theme` | string | Thematic focus |
|
||||||
|
| `tone` | string | Emotional tone |
|
||||||
|
| `draft_file` | string | Path to linked draft file |
|
||||||
|
| `order` | int | Sort order |
|
||||||
|
|
||||||
|
### Draft File Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `id` | string | Unique identifier (e.g., `draft_scene_01_01_01`) |
|
||||||
|
| `title` | string | Scene/chapter title |
|
||||||
|
| `slug` | string | URL-friendly identifier |
|
||||||
|
| `act` | int | Act number |
|
||||||
|
| `chapter` | int | Chapter number |
|
||||||
|
| `scene` | int | Scene number |
|
||||||
|
| `type` | string | `draft` |
|
||||||
|
| `status` | string | `not_started` \| `in_progress` \| `completed` \| `needs_revision` |
|
||||||
|
| `word_count` | int | Current word count |
|
||||||
|
| `word_count_updated` | string | Last word count update date |
|
||||||
|
| `session_word_count` | int | Words written in current session |
|
||||||
|
| `last_session` | string | Date of last writing session |
|
||||||
|
| `total_sessions` | int | Number of writing sessions |
|
||||||
|
| `pov` | string | Point of view character |
|
||||||
|
| `version` | int | Draft version |
|
||||||
|
| `planning_template` | string | Path to linked planning file |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration with Your Workflow
|
||||||
|
|
||||||
|
### With Drag-and-Drop Reordering
|
||||||
|
|
||||||
|
If your editor supports drag-and-drop chapter reordering, the `order` field in the frontmatter is available for sorting. Update this field when reordering scenes.
|
||||||
|
|
||||||
|
### With File Watchers
|
||||||
|
|
||||||
|
To automatically create corresponding files when a new file is created:
|
||||||
|
|
||||||
|
1. **Option A: Use the script manually** (recommended)
|
||||||
|
```bash
|
||||||
|
# After creating a planning file manually, run:
|
||||||
|
python scripts/create_scene.py --from-planning 05-Plot/act_1/scene_05.md
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Option B: Set up a file watcher**
|
||||||
|
Create a watcher script that triggers `create_scene.py` when new files are detected.
|
||||||
|
|
||||||
|
3. **Option C: Editor integration**
|
||||||
|
If your editor supports custom commands, add a command that calls:
|
||||||
|
```bash
|
||||||
|
python scripts/create_scene.py --from-draft "${file}"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Naming Convention**
|
||||||
|
- Planning: `scene_{number:02d}.md` (e.g., `scene_01.md`, `scene_02.md`)
|
||||||
|
- Draft: `scene{number:02d}.md` (e.g., `scene01.md`, `scene02.md`)
|
||||||
|
- Note the slight difference (underscore vs. no underscore)
|
||||||
|
|
||||||
|
2. **Folder Structure**
|
||||||
|
- Planning: `05-Plot/act_{N}/`
|
||||||
|
- Draft: `06-First Draft/Act N/`
|
||||||
|
- Note the difference in folder naming (act_1 vs. Act 1)
|
||||||
|
|
||||||
|
3. **ID Generation**
|
||||||
|
- Planning IDs: `scene_{act:02d}_{chapter:02d}_{scene:02d}`
|
||||||
|
- Draft IDs: `draft_scene_{act:02d}_{chapter:02d}_{scene:02d}`
|
||||||
|
|
||||||
|
4. **Word Count Targets**
|
||||||
|
- Set in planning file
|
||||||
|
- Displayed in draft file
|
||||||
|
- Not automatically synced (manual update or via script)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Module not found: yaml"
|
||||||
|
```bash
|
||||||
|
pip install pyyaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### File paths not working
|
||||||
|
- Ensure you're running the script from the project root
|
||||||
|
- Check that the `scripts/` directory is in your project
|
||||||
|
- Verify relative paths are correct for your folder structure
|
||||||
|
|
||||||
|
### Frontmatter not parsing
|
||||||
|
- Ensure your files start with `---` on the first line
|
||||||
|
- Ensure there's a closing `---` after the frontmatter
|
||||||
|
- YAML is sensitive to indentation (use spaces, not tabs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Custom Metadata
|
||||||
|
|
||||||
|
Add any additional fields to the frontmatter:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/create_scene.py --scene 1 --custom_field "value"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bulk Operations
|
||||||
|
|
||||||
|
Create multiple scenes at once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create scenes 1-10 for chapter 1
|
||||||
|
for i in {1..10}; do
|
||||||
|
python scripts/create_scene.py --chapter 1 --scene $i
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync Existing Files
|
||||||
|
|
||||||
|
If you have existing files without links, you can manually add the frontmatter:
|
||||||
|
|
||||||
|
**Planning file:**
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
draft_file: "../../06-First Draft/Act 1/scene01.md"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
**Draft file:**
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
planning_template: "../../05-Plot/act_1/scene_01.md"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- `05-Plot/_template.md` - Planning file template
|
||||||
|
- `06-First Draft/_template.md` - Draft file template
|
||||||
|
- `03-Story-Bible/` - Story Bible for character/location/lore tracking
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Scene Creation Script
|
||||||
|
|
||||||
|
Creates linked planning and draft files for a new scene.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python create_scene.py --act 1 --chapter 1 --scene 2 --title "Chapter Two: The Journey"
|
||||||
|
|
||||||
|
Or with minimal args (auto-generates title):
|
||||||
|
python create_scene.py --act 1 --chapter 1 --scene 2
|
||||||
|
|
||||||
|
Or create from existing planning file:
|
||||||
|
python create_scene.py --from-planning 05-Plot/act_1/scene_01.md
|
||||||
|
|
||||||
|
Or create from existing draft file:
|
||||||
|
python create_scene.py --from-draft "06-First Draft/Act 1/scene01.md"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Get the project root (parent of scripts directory)
|
||||||
|
SCRIPTS_DIR = Path(__file__).parent
|
||||||
|
PROJECT_ROOT = SCRIPTS_DIR.parent
|
||||||
|
|
||||||
|
# Template paths
|
||||||
|
PLANNING_TEMPLATE = PROJECT_ROOT / "05-Plot" / "_template.md"
|
||||||
|
DRAFT_TEMPLATE = PROJECT_ROOT / "06-First Draft" / "_template.md"
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(text):
|
||||||
|
"""Convert text to URL-friendly slug."""
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
# Convert to lowercase
|
||||||
|
text = text.lower()
|
||||||
|
# Remove special characters
|
||||||
|
text = re.sub(r'[^\w\s-]', '', text)
|
||||||
|
# Replace spaces with hyphens
|
||||||
|
text = re.sub(r'[\s_]+', '-', text)
|
||||||
|
# Remove leading/trailing hyphens
|
||||||
|
text = text.strip('-')
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def generate_title(act, chapter, scene):
|
||||||
|
"""Generate a default title based on act/chapter/scene."""
|
||||||
|
return f"Chapter {chapter}: Scene {scene}"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_yaml_frontmatter(content):
|
||||||
|
"""Parse YAML frontmatter from markdown file."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
if not content.startswith('---'):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Find the frontmatter
|
||||||
|
lines = content.split('\n')
|
||||||
|
if lines[0].strip() == '---':
|
||||||
|
frontmatter_lines = []
|
||||||
|
for line in lines[1:]:
|
||||||
|
if line.strip() == '---':
|
||||||
|
break
|
||||||
|
frontmatter_lines.append(line)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return yaml.safe_load('\n'.join(frontmatter_lines)) or {}
|
||||||
|
except yaml.YAMLError:
|
||||||
|
return {}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def update_frontmatter(content, updates):
|
||||||
|
"""Update YAML frontmatter in markdown content."""
|
||||||
|
lines = content.split('\n')
|
||||||
|
|
||||||
|
if not lines[0].strip() == '---':
|
||||||
|
# No frontmatter, add it
|
||||||
|
yaml_content = "\n".join(f"{k}: {v}" for k, v in updates.items())
|
||||||
|
return f"---\n{yaml_content}\n---\n\n" + content
|
||||||
|
|
||||||
|
# Find and replace frontmatter
|
||||||
|
in_frontmatter = False
|
||||||
|
frontmatter_start = 0
|
||||||
|
frontmatter_end = 0
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip() == '---':
|
||||||
|
if not in_frontmatter:
|
||||||
|
frontmatter_start = i
|
||||||
|
in_frontmatter = True
|
||||||
|
else:
|
||||||
|
frontmatter_end = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if frontmatter_end == 0:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# Parse existing frontmatter
|
||||||
|
existing = parse_yaml_frontmatter(content)
|
||||||
|
existing.update(updates)
|
||||||
|
|
||||||
|
# Generate new frontmatter
|
||||||
|
yaml_lines = ["---"]
|
||||||
|
for key, value in existing.items():
|
||||||
|
if isinstance(value, str):
|
||||||
|
yaml_lines.append(f"{key}: \"{value}\"")
|
||||||
|
elif isinstance(value, list):
|
||||||
|
yaml_lines.append(f"{key}: {value}")
|
||||||
|
else:
|
||||||
|
yaml_lines.append(f"{key}: {value}")
|
||||||
|
yaml_lines.append("---")
|
||||||
|
|
||||||
|
# Reconstruct content
|
||||||
|
new_content = "\n".join(yaml_lines) + "\n\n" + "\n".join(lines[frontmatter_end + 1:])
|
||||||
|
return new_content
|
||||||
|
|
||||||
|
|
||||||
|
def create_planning_file(act, chapter, scene, title, slug=None, **kwargs):
|
||||||
|
"""Create a new planning file."""
|
||||||
|
if slug is None:
|
||||||
|
slug = slugify(title)
|
||||||
|
|
||||||
|
# Create path
|
||||||
|
act_dir = PROJECT_ROOT / "05-Plot" / f"act_{act}"
|
||||||
|
act_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
filename = act_dir / f"scene_{scene:02d}.md"
|
||||||
|
|
||||||
|
# Read template
|
||||||
|
with open(PLANNING_TEMPLATE, 'r') as f:
|
||||||
|
template_content = f.read()
|
||||||
|
|
||||||
|
# Generate ID
|
||||||
|
scene_id = f"scene_{act:02d}_{chapter:02d}_{scene:02d}"
|
||||||
|
|
||||||
|
# Prepare frontmatter updates
|
||||||
|
frontmatter = {
|
||||||
|
'id': scene_id,
|
||||||
|
'title': title,
|
||||||
|
'slug': slug,
|
||||||
|
'act': act,
|
||||||
|
'chapter': chapter,
|
||||||
|
'scene': scene,
|
||||||
|
'order': scene,
|
||||||
|
'created': datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
'updated': datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update with any additional kwargs
|
||||||
|
frontmatter.update(kwargs)
|
||||||
|
|
||||||
|
# Update draft_file path if not provided
|
||||||
|
if 'draft_file' not in frontmatter:
|
||||||
|
draft_path = f"../../06-First Draft/Act {act}/scene{scene:02d}.md"
|
||||||
|
frontmatter['draft_file'] = draft_path
|
||||||
|
|
||||||
|
# Update content
|
||||||
|
content = update_frontmatter(template_content, frontmatter)
|
||||||
|
|
||||||
|
# Write file
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def create_draft_file(act, chapter, scene, title, slug=None, planning_path=None, **kwargs):
|
||||||
|
"""Create a new draft file."""
|
||||||
|
if slug is None:
|
||||||
|
slug = slugify(title)
|
||||||
|
|
||||||
|
# Create path
|
||||||
|
act_dir = PROJECT_ROOT / "06-First Draft" / f"Act {act}"
|
||||||
|
act_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
filename = act_dir / f"scene{scene:02d}.md"
|
||||||
|
|
||||||
|
# Read template
|
||||||
|
with open(DRAFT_TEMPLATE, 'r') as f:
|
||||||
|
template_content = f.read()
|
||||||
|
|
||||||
|
# Generate ID
|
||||||
|
scene_id = f"draft_scene_{act:02d}_{chapter:02d}_{scene:02d}"
|
||||||
|
|
||||||
|
# Prepare frontmatter updates
|
||||||
|
frontmatter = {
|
||||||
|
'id': scene_id,
|
||||||
|
'title': title,
|
||||||
|
'slug': slug,
|
||||||
|
'act': act,
|
||||||
|
'chapter': chapter,
|
||||||
|
'scene': scene,
|
||||||
|
'created': datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
'updated': datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update with any additional kwargs
|
||||||
|
frontmatter.update(kwargs)
|
||||||
|
|
||||||
|
# Set planning_template path if not provided
|
||||||
|
if 'planning_template' not in frontmatter:
|
||||||
|
if planning_path:
|
||||||
|
# Use the provided planning path
|
||||||
|
relative_path = os.path.relpath(planning_path, act_dir)
|
||||||
|
frontmatter['planning_template'] = relative_path
|
||||||
|
else:
|
||||||
|
# Generate default path
|
||||||
|
planning_path = f"../../05-Plot/act_{act}/scene_{scene:02d}.md"
|
||||||
|
frontmatter['planning_template'] = planning_path
|
||||||
|
|
||||||
|
# Update content
|
||||||
|
content = update_frontmatter(template_content, frontmatter)
|
||||||
|
|
||||||
|
# Write file
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def link_files(planning_path, draft_path):
|
||||||
|
"""Ensure both files are linked to each other."""
|
||||||
|
# Update planning file with draft path
|
||||||
|
with open(planning_path, 'r') as f:
|
||||||
|
planning_content = f.read()
|
||||||
|
|
||||||
|
relative_draft = os.path.relpath(draft_path, Path(planning_path).parent)
|
||||||
|
planning_content = update_frontmatter(planning_content, {
|
||||||
|
'draft_file': relative_draft
|
||||||
|
})
|
||||||
|
|
||||||
|
with open(planning_path, 'w') as f:
|
||||||
|
f.write(planning_content)
|
||||||
|
|
||||||
|
# Update draft file with planning path
|
||||||
|
with open(draft_path, 'r') as f:
|
||||||
|
draft_content = f.read()
|
||||||
|
|
||||||
|
relative_planning = os.path.relpath(planning_path, Path(draft_path).parent)
|
||||||
|
draft_content = update_frontmatter(draft_content, {
|
||||||
|
'planning_template': relative_planning
|
||||||
|
})
|
||||||
|
|
||||||
|
with open(draft_path, 'w') as f:
|
||||||
|
f.write(draft_content)
|
||||||
|
|
||||||
|
|
||||||
|
def create_scene_pair(act, chapter, scene, title=None, **kwargs):
|
||||||
|
"""Create both planning and draft files."""
|
||||||
|
if title is None:
|
||||||
|
title = generate_title(act, chapter, scene)
|
||||||
|
|
||||||
|
slug = slugify(title)
|
||||||
|
|
||||||
|
# Create planning file
|
||||||
|
planning_path = create_planning_file(
|
||||||
|
act, chapter, scene, title, slug, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create draft file (without planning_path initially)
|
||||||
|
draft_path = create_draft_file(
|
||||||
|
act, chapter, scene, title, slug, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link them together
|
||||||
|
link_files(planning_path, draft_path)
|
||||||
|
|
||||||
|
print(f"✓ Created planning file: {planning_path.relative_to(PROJECT_ROOT)}")
|
||||||
|
print(f"✓ Created draft file: {draft_path.relative_to(PROJECT_ROOT)}")
|
||||||
|
print(f"✓ Linked files together")
|
||||||
|
|
||||||
|
return planning_path, draft_path
|
||||||
|
|
||||||
|
|
||||||
|
def create_from_planning(planning_path_str):
|
||||||
|
"""Create a draft file from an existing planning file."""
|
||||||
|
planning_path = Path(planning_path_str)
|
||||||
|
|
||||||
|
if not planning_path.exists():
|
||||||
|
print(f"Error: Planning file not found: {planning_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Parse planning file
|
||||||
|
with open(planning_path, 'r') as f:
|
||||||
|
planning_content = f.read()
|
||||||
|
|
||||||
|
frontmatter = parse_yaml_frontmatter(planning_content)
|
||||||
|
|
||||||
|
if not frontmatter:
|
||||||
|
print(f"Error: No valid frontmatter in {planning_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
act = frontmatter.get('act', 1)
|
||||||
|
chapter = frontmatter.get('chapter', 1)
|
||||||
|
scene = frontmatter.get('scene', 1)
|
||||||
|
title = frontmatter.get('title', generate_title(act, chapter, scene))
|
||||||
|
|
||||||
|
# Create draft file
|
||||||
|
draft_path = create_draft_file(
|
||||||
|
act, chapter, scene, title,
|
||||||
|
planning_path=planning_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link files
|
||||||
|
link_files(planning_path, draft_path)
|
||||||
|
|
||||||
|
print(f"✓ Created draft file: {draft_path.relative_to(PROJECT_ROOT)}")
|
||||||
|
print(f"✓ Linked to planning file: {planning_path.relative_to(PROJECT_ROOT)}")
|
||||||
|
|
||||||
|
return draft_path
|
||||||
|
|
||||||
|
|
||||||
|
def create_from_draft(draft_path_str):
|
||||||
|
"""Create a planning file from an existing draft file."""
|
||||||
|
draft_path = Path(draft_path_str)
|
||||||
|
|
||||||
|
if not draft_path.exists():
|
||||||
|
print(f"Error: Draft file not found: {draft_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Parse draft file
|
||||||
|
with open(draft_path, 'r') as f:
|
||||||
|
draft_content = f.read()
|
||||||
|
|
||||||
|
frontmatter = parse_yaml_frontmatter(draft_content)
|
||||||
|
|
||||||
|
if not frontmatter:
|
||||||
|
print(f"Error: No valid frontmatter in {draft_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
act = frontmatter.get('act', 1)
|
||||||
|
chapter = frontmatter.get('chapter', 1)
|
||||||
|
scene = frontmatter.get('scene', 1)
|
||||||
|
title = frontmatter.get('title', generate_title(act, chapter, scene))
|
||||||
|
|
||||||
|
# Create planning file
|
||||||
|
planning_path = create_planning_file(
|
||||||
|
act, chapter, scene, title,
|
||||||
|
draft_file=draft_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link files
|
||||||
|
link_files(planning_path, draft_path)
|
||||||
|
|
||||||
|
print(f"✓ Created planning file: {planning_path.relative_to(PROJECT_ROOT)}")
|
||||||
|
print(f"✓ Linked to draft file: {draft_path.relative_to(PROJECT_ROOT)}")
|
||||||
|
|
||||||
|
return planning_path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Create linked planning and draft files for scenes.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# For creating new scene pair
|
||||||
|
parser.add_argument('--act', type=int, default=1, help='Act number')
|
||||||
|
parser.add_argument('--chapter', type=int, default=1, help='Chapter number')
|
||||||
|
parser.add_argument('--scene', type=int, required=True, help='Scene number')
|
||||||
|
parser.add_argument('--title', type=str, default=None, help='Scene title')
|
||||||
|
parser.add_argument('--type', type=str, default='scene',
|
||||||
|
choices=['scene', 'chapter', 'interlude'],
|
||||||
|
help='Scene type')
|
||||||
|
|
||||||
|
# For creating from existing files
|
||||||
|
parser.add_argument('--from-planning', type=str, default=None,
|
||||||
|
help='Create draft from existing planning file')
|
||||||
|
parser.add_argument('--from-draft', type=str, default=None,
|
||||||
|
help='Create planning from existing draft file')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.from_planning:
|
||||||
|
create_from_planning(args.from_planning)
|
||||||
|
elif args.from_draft:
|
||||||
|
create_from_draft(args.from_draft)
|
||||||
|
else:
|
||||||
|
create_scene_pair(
|
||||||
|
act=args.act,
|
||||||
|
chapter=args.chapter,
|
||||||
|
scene=args.scene,
|
||||||
|
title=args.title,
|
||||||
|
type=args.type
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user