ro 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/Gemfile.lock +57 -10
 - data/LICENSE +1 -1
 - data/MIGRATION.md +320 -0
 - data/README.md +286 -111
 - data/Rakefile +2 -2
 - data/a.yml +60 -0
 - data/bin/ro +10 -0
 - data/lib/ro/_lib.rb +18 -6
 - data/lib/ro/asset.rb +67 -16
 - data/lib/ro/collection.rb +91 -10
 - data/lib/ro/config.rb +4 -0
 - data/lib/ro/error.rb +5 -2
 - data/lib/ro/html.rb +23 -0
 - data/lib/ro/html_safe.rb +143 -0
 - data/lib/ro/methods.rb +95 -38
 - data/lib/ro/migrator.rb +285 -0
 - data/lib/ro/node.rb +128 -45
 - data/lib/ro/path.rb +4 -0
 - data/lib/ro/root.rb +75 -1
 - data/lib/ro/script/migrate.rb +204 -0
 - data/lib/ro/script/server.rb +1 -1
 - data/lib/ro/template.rb +62 -22
 - data/lib/ro/text.rb +120 -0
 - data/lib/ro.rb +5 -0
 - data/public/api/ro/index-1.json +997 -79
 - data/public/api/ro/index.json +997 -79
 - data/public/api/ro/nerd/fastest-possible-embeddings/index.json +90 -0
 - data/public/api/ro/nerd/ima/index.json +49 -0
 - data/public/api/ro/nerd/index/index.json +74 -0
 - data/public/api/ro/nerd/index-1.json +204 -0
 - data/public/api/ro/nerd/index.json +194 -0
 - data/public/api/ro/pages/about/index.json +60 -0
 - data/public/api/ro/pages/contact/index.json +50 -0
 - data/public/api/ro/pages/cv/index.json +49 -0
 - data/public/api/ro/pages/disco/index.json +117 -0
 - data/public/api/ro/pages/index/index.json +30 -0
 - data/public/api/ro/pages/index-1.json +366 -0
 - data/public/api/ro/pages/index.json +356 -0
 - data/public/api/ro/pages/jess/index.json +62 -0
 - data/public/api/ro/pages/now/index.json +43 -0
 - data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +265 -0
 - data/public/api/ro/posts/facebook-and-global-extremism/index.json +90 -0
 - data/public/api/ro/posts/index-1.json +461 -79
 - data/public/api/ro/posts/index.json +461 -79
 - data/public/api/ro/posts/lemmings-considered-harmful/index.json +49 -0
 - data/public/api/ro/posts/lost-in-the-desert/index.json +49 -0
 - data/public/api/ro/posts/mission/index.json +49 -0
 - data/public/api/ro/posts/return-your-laptop/index.json +61 -0
 - data/public/ro/nerd/fastest-possible-embeddings/assets/giraffe.jpeg +0 -0
 - data/public/ro/nerd/fastest-possible-embeddings/assets/let-me-in.jpg +0 -0
 - data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.js +70 -0
 - data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.rs +68 -0
 - data/public/ro/nerd/fastest-possible-embeddings/assets/terminal.jpg +0 -0
 - data/public/ro/nerd/fastest-possible-embeddings/body.md +266 -0
 - data/public/ro/nerd/fastest-possible-embeddings.yml +7 -0
 - data/public/ro/nerd/ima/assets/og.jpeg +0 -0
 - data/public/ro/nerd/ima/body.md +22 -0
 - data/public/ro/nerd/ima.yml +8 -0
 - data/public/ro/nerd/index/assets/giraffe.jpeg +0 -0
 - data/public/ro/nerd/index/assets/let-me-in.jpg +0 -0
 - data/public/ro/nerd/index/assets/terminal.jpg +0 -0
 - data/public/ro/nerd/index/body.md +130 -0
 - data/public/ro/nerd/index.yml +7 -0
 - data/public/ro/pages/about/assets/og.jpeg +0 -0
 - data/public/ro/pages/about/assets/speak-english-pulp-fiction.gif +0 -0
 - data/public/ro/pages/about/body.md +40 -0
 - data/public/ro/pages/contact/assets/giraffe.jpeg +0 -0
 - data/public/ro/pages/contact/body.md +9 -0
 - data/public/ro/pages/contact.yml +7 -0
 - data/public/ro/pages/cv/assets/ara.jpg +0 -0
 - data/public/ro/pages/cv/body.md +122 -0
 - data/public/ro/pages/cv.yml +6 -0
 - data/public/ro/pages/disco/assets/disco.jpg +0 -0
 - data/public/ro/pages/disco/assets/disco.png +0 -0
 - data/public/ro/pages/disco/assets/speak-english-pulp-fiction.gif +0 -0
 - data/public/ro/pages/disco/assets/src/environment.md +2354 -0
 - data/public/ro/pages/disco/assets/src/fortune-500.md +2518 -0
 - data/public/ro/pages/disco/assets/src/greed.md +2703 -0
 - data/public/ro/pages/disco/assets/src/up-at-night.md +2337 -0
 - data/public/ro/pages/disco/body.md +99 -0
 - data/public/ro/pages/disco/samples/environment.md +2354 -0
 - data/public/ro/pages/disco/samples/fortune-500.md +2518 -0
 - data/public/ro/pages/disco/samples/greed.md +2703 -0
 - data/public/ro/pages/disco/samples/up-at-night.md +2337 -0
 - data/public/ro/pages/disco.yml +9 -0
 - data/public/ro/pages/index/body.md +15 -0
 - data/public/ro/pages/index.yml +1 -0
 - data/public/ro/pages/jess/assets/og.jpg +0 -0
 - data/public/ro/pages/jess/assets/speak-english-pulp-fiction.gif +0 -0
 - data/public/ro/pages/jess/body.md +3 -0
 - data/public/ro/pages/jess.yml +7 -0
 - data/public/ro/pages/now/assets/speak-english-pulp-fiction.gif +0 -0
 - data/public/ro/pages/now/body.md +24 -0
 - data/public/ro/pages/now.yml +1 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image10.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image11.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image12.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image13.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image14.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image15.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image2.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image3.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image4.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image5.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image6.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image7.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image8.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/image9.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/josh-pointing.jpg +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/levi-rawr.png +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/assets/purple-heart.jpg +0 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave/body.md +419 -0
 - data/public/ro/posts/almost-died-in-an-ice-cave.yml +6 -0
 - data/public/ro/posts/facebook-and-global-extremism/assets/background.html +125 -0
 - data/public/ro/posts/facebook-and-global-extremism/assets/background.md +95 -0
 - data/public/ro/posts/facebook-and-global-extremism/assets/og.jpg +0 -0
 - data/public/ro/posts/facebook-and-global-extremism/assets/prompt.txt +122 -0
 - data/public/ro/posts/facebook-and-global-extremism/assets/results.md +183 -0
 - data/public/ro/posts/facebook-and-global-extremism/assets/survey.txt +190 -0
 - data/public/ro/posts/facebook-and-global-extremism/body.md +393 -0
 - data/public/ro/posts/facebook-and-global-extremism.yml +7 -0
 - data/public/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg +0 -0
 - data/public/ro/posts/lemmings-considered-harmful/body.md +43 -0
 - data/public/ro/posts/lemmings-considered-harmful.yml +6 -0
 - data/public/ro/posts/lost-in-the-desert/assets/og.jpg +0 -0
 - data/public/ro/posts/lost-in-the-desert/body.md +7 -0
 - data/public/ro/posts/lost-in-the-desert.yml +6 -0
 - data/public/ro/posts/mission/assets/og.jpg +0 -0
 - data/public/ro/posts/mission/body.md +4 -0
 - data/public/ro/posts/mission.yml +6 -0
 - data/public/ro/posts/return-your-laptop/assets/og.jpg +0 -0
 - data/public/ro/posts/return-your-laptop/assets/return-your-laptop.png +0 -0
 - data/public/ro/posts/return-your-laptop/body.md +58 -0
 - data/public/ro/posts/return-your-laptop.yml +6 -0
 - data/ro.gemspec +369 -49
 - data/scripts/speedtest.rb +324 -0
 - data/specs/001-simplify-asset-structure/IMPLEMENTATION_SUMMARY.md +212 -0
 - data/specs/001-simplify-asset-structure/checklists/requirements.md +36 -0
 - data/specs/001-simplify-asset-structure/contracts/collection_api.md +407 -0
 - data/specs/001-simplify-asset-structure/contracts/migrator_api.md +461 -0
 - data/specs/001-simplify-asset-structure/contracts/node_api.md +294 -0
 - data/specs/001-simplify-asset-structure/data-model.md +381 -0
 - data/specs/001-simplify-asset-structure/plan.md +90 -0
 - data/specs/001-simplify-asset-structure/quickstart.md +575 -0
 - data/specs/001-simplify-asset-structure/research.md +333 -0
 - data/specs/001-simplify-asset-structure/spec.md +127 -0
 - data/specs/001-simplify-asset-structure/tasks.md +349 -0
 - data/test/fixtures/new_structure/mixed/test-json.json +5 -0
 - data/test/fixtures/new_structure/mixed/test-yaml.yml +3 -0
 - data/test/fixtures/new_structure/posts/metadata-only.yml +7 -0
 - data/test/fixtures/new_structure/posts/nested-test/assets/subdirectory/image.png +2 -0
 - data/test/fixtures/new_structure/posts/nested-test.yml +7 -0
 - data/test/fixtures/new_structure/posts/sample-post/assets/body.md +5 -0
 - data/test/fixtures/new_structure/posts/sample-post/assets/image.jpg +2 -0
 - data/test/fixtures/new_structure/posts/sample-post.yml +7 -0
 - data/test/fixtures/old_structure/posts/assets-only/assets/test.txt +1 -0
 - data/test/fixtures/old_structure/posts/sample-post/assets/body.md +5 -0
 - data/test/fixtures/old_structure/posts/sample-post/assets/image.jpg +2 -0
 - data/test/fixtures/old_structure/posts/sample-post/attributes.yml +2 -0
 - data/test/integration/ro_integration_test.rb +165 -0
 - data/test/test_helper.rb +149 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post.yml +7 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post.yml +7 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/new_structure_test_1760746452/mixed/test-json.json +5 -0
 - data/test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml +3 -0
 - data/test/tmp/new_structure_test_1760746452/posts/metadata-only.yml +7 -0
 - data/test/tmp/new_structure_test_1760746452/posts/nested-test/subdirectory/image.png +2 -0
 - data/test/tmp/new_structure_test_1760746452/posts/nested-test.yml +7 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post/body.md +5 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post.yml +7 -0
 - data/test/unit/asset_test.rb +90 -0
 - data/test/unit/collection_test.rb +127 -0
 - data/test/unit/migrator_test.rb +209 -0
 - data/test/unit/node_test.rb +138 -0
 - data/tmp/gem-details.oe +0 -0
 - metadata +250 -33
 - data/public/api/ro/posts/first_post/index.json +0 -52
 - data/public/api/ro/posts/second_post/index.json +0 -51
 - data/public/api/ro/posts/third_post/index.json +0 -51
 - data/public/ro/posts/first_post/assets/foo/bar/baz.jpg +0 -0
 - data/public/ro/posts/first_post/assets/foo.jpg +0 -0
 - data/public/ro/posts/first_post/assets/src/foo/bar.rb +0 -3
 - data/public/ro/posts/first_post/attributes.yml +0 -2
 - data/public/ro/posts/first_post/blurb.erb.md +0 -7
 - data/public/ro/posts/first_post/body.md +0 -16
 - data/public/ro/posts/first_post/testing.txt +0 -3
 - data/public/ro/posts/second_post/assets/foo/bar/baz.jpg +0 -0
 - data/public/ro/posts/second_post/assets/foo.jpg +0 -0
 - data/public/ro/posts/second_post/assets/src/foo/bar.rb +0 -3
 - data/public/ro/posts/second_post/attributes.yml +0 -2
 - data/public/ro/posts/second_post/blurb.erb.md +0 -5
 - data/public/ro/posts/second_post/body.md +0 -16
 - data/public/ro/posts/third_post/assets/foo/bar/baz.jpg +0 -0
 - data/public/ro/posts/third_post/assets/foo.jpg +0 -0
 - data/public/ro/posts/third_post/assets/src/foo/bar.rb +0 -3
 - data/public/ro/posts/third_post/attributes.yml +0 -2
 - data/public/ro/posts/third_post/blurb.erb.md +0 -5
 - data/public/ro/posts/third_post/body.md +0 -16
 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Implementation Plan: Simplify Asset Directory Structure
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            **Branch**: `001-simplify-asset-structure` | **Date**: 2025-10-17 | **Spec**: [spec.md](./spec.md)
         
     | 
| 
      
 4 
     | 
    
         
            +
            **Input**: Feature specification from `/specs/001-simplify-asset-structure/spec.md`
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            ## Summary
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            Refactor the ro gem's asset directory structure from nested format (`identifier/attributes.yml` + `identifier/assets/`) to a flattened format (`identifier.yml` + `identifier/`). This breaking change (v4.x → v5.0) simplifies the directory hierarchy by one level, making asset organization more intuitive. Implementation requires modifying core Node, Collection, and Asset classes, creating a migration tool, and establishing comprehensive test coverage (currently no tests exist).
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ## Technical Context
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            **Language/Version**: Ruby 3.0+
         
     | 
| 
      
 15 
     | 
    
         
            +
            **Primary Dependencies**: map (~> 6.6), kramdown (~> 2.4), front_matter_parser (~> 1.0), nokogiri (~> 1)
         
     | 
| 
      
 16 
     | 
    
         
            +
            **Storage**: File system (YAML/JSON/TOML metadata files + asset directories)
         
     | 
| 
      
 17 
     | 
    
         
            +
            **Testing**: Custom test runner via Rake (test/unit, test/functional, test/integration)
         
     | 
| 
      
 18 
     | 
    
         
            +
            **Target Platform**: Cross-platform (Linux, macOS, Windows) - Ruby gem
         
     | 
| 
      
 19 
     | 
    
         
            +
            **Project Type**: Single project (Ruby gem library + CLI tool)
         
     | 
| 
      
 20 
     | 
    
         
            +
            **Performance Goals**: <100ms asset lookup for collections with 10,000 assets (per spec SC-001)
         
     | 
| 
      
 21 
     | 
    
         
            +
            **Constraints**: Zero data loss during migration (per spec SC-002), backward compatibility preference for old structure until migration
         
     | 
| 
      
 22 
     | 
    
         
            +
            **Scale/Scope**: Codebase has ~10 core classes, 10+ example assets in public/ro/, breaking change affects all ro users
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            ## Constitution Check
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            **Status**: SKIPPED - No project constitution file found at `.specify/memory/constitution.md`. This feature proceeds without constitutional constraints.
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            **Note**: If this project adopts a constitution in the future, this feature should be reviewed against those principles.
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            ## Project Structure
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            ### Documentation (this feature)
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            ```
         
     | 
| 
      
 37 
     | 
    
         
            +
            specs/[###-feature]/
         
     | 
| 
      
 38 
     | 
    
         
            +
            ├── plan.md              # This file (/speckit.plan command output)
         
     | 
| 
      
 39 
     | 
    
         
            +
            ├── research.md          # Phase 0 output (/speckit.plan command)
         
     | 
| 
      
 40 
     | 
    
         
            +
            ├── data-model.md        # Phase 1 output (/speckit.plan command)
         
     | 
| 
      
 41 
     | 
    
         
            +
            ├── quickstart.md        # Phase 1 output (/speckit.plan command)
         
     | 
| 
      
 42 
     | 
    
         
            +
            ├── contracts/           # Phase 1 output (/speckit.plan command)
         
     | 
| 
      
 43 
     | 
    
         
            +
            └── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
         
     | 
| 
      
 44 
     | 
    
         
            +
            ```
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            ### Source Code (repository root)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            ```
         
     | 
| 
      
 49 
     | 
    
         
            +
            lib/ro/
         
     | 
| 
      
 50 
     | 
    
         
            +
            ├── node.rb              # MODIFY: Core node class - loads attributes, manages assets
         
     | 
| 
      
 51 
     | 
    
         
            +
            ├── collection.rb        # MODIFY: Collection class - discovers nodes by new pattern
         
     | 
| 
      
 52 
     | 
    
         
            +
            ├── asset.rb            # MODIFY: Asset class - path resolution for new structure
         
     | 
| 
      
 53 
     | 
    
         
            +
            ├── root.rb             # MINOR: May need updates for collection discovery
         
     | 
| 
      
 54 
     | 
    
         
            +
            ├── methods.rb          # REVIEW: URL generation, may need path updates
         
     | 
| 
      
 55 
     | 
    
         
            +
            ├── path.rb             # REVIEW: Path utilities, may need helpers
         
     | 
| 
      
 56 
     | 
    
         
            +
            ├── template.rb         # NO CHANGE: Template rendering (md, yml, etc)
         
     | 
| 
      
 57 
     | 
    
         
            +
            ├── script/
         
     | 
| 
      
 58 
     | 
    
         
            +
            │   └── migrator.rb     # NEW: Migration script for old → new structure
         
     | 
| 
      
 59 
     | 
    
         
            +
            └── ... (other files unchanged)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            test/                   # NEW: Test directory (currently doesn't exist)
         
     | 
| 
      
 62 
     | 
    
         
            +
            ├── unit/
         
     | 
| 
      
 63 
     | 
    
         
            +
            │   ├── node_test.rb    # NEW: Unit tests for Node class
         
     | 
| 
      
 64 
     | 
    
         
            +
            │   ├── collection_test.rb  # NEW: Unit tests for Collection class
         
     | 
| 
      
 65 
     | 
    
         
            +
            │   ├── asset_test.rb   # NEW: Unit tests for Asset class
         
     | 
| 
      
 66 
     | 
    
         
            +
            │   └── migrator_test.rb    # NEW: Unit tests for migration tool
         
     | 
| 
      
 67 
     | 
    
         
            +
            ├── integration/
         
     | 
| 
      
 68 
     | 
    
         
            +
            │   └── ro_integration_test.rb  # NEW: End-to-end tests
         
     | 
| 
      
 69 
     | 
    
         
            +
            └── fixtures/           # NEW: Test data in both old and new structures
         
     | 
| 
      
 70 
     | 
    
         
            +
                ├── old_structure/
         
     | 
| 
      
 71 
     | 
    
         
            +
                └── new_structure/
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            public/ro/              # MIGRATE: Example content (test migration here)
         
     | 
| 
      
 74 
     | 
    
         
            +
            ├── posts/
         
     | 
| 
      
 75 
     | 
    
         
            +
            │   └── almost-died-in-an-ice-cave.yml  # MIGRATED: Was attributes.yml
         
     | 
| 
      
 76 
     | 
    
         
            +
            │   └── almost-died-in-an-ice-cave/     # MIGRATED: Was assets/ + other files
         
     | 
| 
      
 77 
     | 
    
         
            +
            │       ├── body.md
         
     | 
| 
      
 78 
     | 
    
         
            +
            │       ├── image1.png
         
     | 
| 
      
 79 
     | 
    
         
            +
            │       └── og.jpg
         
     | 
| 
      
 80 
     | 
    
         
            +
            └── ... (other collections migrated similarly)
         
     | 
| 
      
 81 
     | 
    
         
            +
            ```
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            **Structure Decision**: Single project (Ruby gem). This is a library that provides both programmatic API and CLI interface. The core logic lives in `lib/ro/`, with the main entry point at `lib/ro.rb`. Tests will be created in a new `test/` directory following the Rake test convention (unit, functional, integration). The `public/ro/` directory contains example content that will be migrated as part of this feature implementation.
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            ## Complexity Tracking
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            *Fill ONLY if Constitution Check has violations that must be justified*
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            **Status**: N/A - No constitution defined, no violations to track.
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,575 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Quickstart: Simplify Asset Directory Structure
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            **Feature**: 001-simplify-asset-structure
         
     | 
| 
      
 4 
     | 
    
         
            +
            **Version**: 5.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            **Date**: 2025-10-17
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## Overview
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            This guide walks you through migrating from the old nested asset structure to the new simplified structure, and demonstrates how to use the new API.
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            ## For Existing Users: Migration Guide
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            ### Step 1: Backup Your Data
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            Before migrating, create a backup of your ro directory:
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 18 
     | 
    
         
            +
            cp -r ./public/ro ./public/ro.backup
         
     | 
| 
      
 19 
     | 
    
         
            +
            ```
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            ### Step 2: Validate Your Structure
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            Check if your assets are in the old format:
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 26 
     | 
    
         
            +
            # Look for the old pattern:
         
     | 
| 
      
 27 
     | 
    
         
            +
            find ./public/ro -name "attributes.yml" -o -name "attributes.yaml"
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            # If you see results like:
         
     | 
| 
      
 30 
     | 
    
         
            +
            # ./public/ro/posts/my-post/attributes.yml
         
     | 
| 
      
 31 
     | 
    
         
            +
            # ./public/ro/pages/about/attributes.yml
         
     | 
| 
      
 32 
     | 
    
         
            +
            # Then you have the old structure and need to migrate.
         
     | 
| 
      
 33 
     | 
    
         
            +
            ```
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            ### Step 3: Run Migration (Dry Run First)
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            Preview the migration without making changes:
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 40 
     | 
    
         
            +
            ro migrate ./public/ro --dry-run --verbose
         
     | 
| 
      
 41 
     | 
    
         
            +
            ```
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            Review the output to ensure it looks correct:
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            ```
         
     | 
| 
      
 46 
     | 
    
         
            +
            [DRY RUN] Migrating collection: posts
         
     | 
| 
      
 47 
     | 
    
         
            +
            [DRY RUN]   Node: my-post
         
     | 
| 
      
 48 
     | 
    
         
            +
            [DRY RUN]     MOVE: posts/my-post/attributes.yml → posts/my-post.yml
         
     | 
| 
      
 49 
     | 
    
         
            +
            [DRY RUN]     MOVE: posts/my-post/assets/cover.jpg → posts/my-post/cover.jpg
         
     | 
| 
      
 50 
     | 
    
         
            +
            [DRY RUN]     MOVE: posts/my-post/body.md → posts/my-post/body.md
         
     | 
| 
      
 51 
     | 
    
         
            +
            [DRY RUN]     REMOVE: posts/my-post/assets/ (empty)
         
     | 
| 
      
 52 
     | 
    
         
            +
            [DRY RUN]   Node: another-post
         
     | 
| 
      
 53 
     | 
    
         
            +
            [DRY RUN]     ...
         
     | 
| 
      
 54 
     | 
    
         
            +
            [DRY RUN] Summary: 15 nodes would be migrated
         
     | 
| 
      
 55 
     | 
    
         
            +
            ```
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            ### Step 4: Run Actual Migration
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            If the dry run looks good, run the real migration with backup:
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 62 
     | 
    
         
            +
            ro migrate ./public/ro --backup --verbose
         
     | 
| 
      
 63 
     | 
    
         
            +
            ```
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            Output:
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            ```
         
     | 
| 
      
 68 
     | 
    
         
            +
            Creating backup at: ./public/ro.backup.20250117-143022
         
     | 
| 
      
 69 
     | 
    
         
            +
            Migrating collection: posts
         
     | 
| 
      
 70 
     | 
    
         
            +
              ✓ my-post migrated successfully
         
     | 
| 
      
 71 
     | 
    
         
            +
              ✓ another-post migrated successfully
         
     | 
| 
      
 72 
     | 
    
         
            +
              ✓ ...
         
     | 
| 
      
 73 
     | 
    
         
            +
            Migrating collection: pages
         
     | 
| 
      
 74 
     | 
    
         
            +
              ✓ about migrated successfully
         
     | 
| 
      
 75 
     | 
    
         
            +
              ✓ ...
         
     | 
| 
      
 76 
     | 
    
         
            +
            ✓ Migration complete!
         
     | 
| 
      
 77 
     | 
    
         
            +
              Total nodes: 15
         
     | 
| 
      
 78 
     | 
    
         
            +
              Migrated: 15
         
     | 
| 
      
 79 
     | 
    
         
            +
              Failed: 0
         
     | 
| 
      
 80 
     | 
    
         
            +
              Backup: ./public/ro.backup.20250117-143022
         
     | 
| 
      
 81 
     | 
    
         
            +
            ```
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            ### Step 5: Verify Migration
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            Check that your assets are now in the new format:
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 88 
     | 
    
         
            +
            # Look for the new pattern:
         
     | 
| 
      
 89 
     | 
    
         
            +
            ls -la ./public/ro/posts/
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
            # You should see:
         
     | 
| 
      
 92 
     | 
    
         
            +
            # my-post.yml           ← Metadata at collection level
         
     | 
| 
      
 93 
     | 
    
         
            +
            # my-post/              ← Asset directory (no more assets/ subdirectory)
         
     | 
| 
      
 94 
     | 
    
         
            +
            #   ├── body.md
         
     | 
| 
      
 95 
     | 
    
         
            +
            #   └── cover.jpg
         
     | 
| 
      
 96 
     | 
    
         
            +
            ```
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            Test that the ro gem can load your assets:
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 101 
     | 
    
         
            +
            ro console
         
     | 
| 
      
 102 
     | 
    
         
            +
            ```
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 105 
     | 
    
         
            +
            root = Ro::Root.new('./public/ro')
         
     | 
| 
      
 106 
     | 
    
         
            +
            posts = root.collection('posts')
         
     | 
| 
      
 107 
     | 
    
         
            +
            post = posts.node_for('my-post')
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
            puts post.attributes[:title]  # Should print the title
         
     | 
| 
      
 110 
     | 
    
         
            +
            puts post.asset_paths          # Should show cover.jpg, etc.
         
     | 
| 
      
 111 
     | 
    
         
            +
            ```
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
            ### Step 6: Remove Old Backup (Optional)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
            Once you've verified everything works, you can remove the old backup:
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 118 
     | 
    
         
            +
            rm -rf ./public/ro.backup.20250117-143022
         
     | 
| 
      
 119 
     | 
    
         
            +
            ```
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            ---
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            ## For New Users: Using the New Structure
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            ### Creating a New ro Directory
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 128 
     | 
    
         
            +
            mkdir -p ./my-ro/posts
         
     | 
| 
      
 129 
     | 
    
         
            +
            cd ./my-ro
         
     | 
| 
      
 130 
     | 
    
         
            +
            ```
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
            ### Creating Your First Asset
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
            Create a metadata file at the collection level:
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
            **posts/my-first-post.yml**:
         
     | 
| 
      
 137 
     | 
    
         
            +
            ```yaml
         
     | 
| 
      
 138 
     | 
    
         
            +
            title: "My First Post"
         
     | 
| 
      
 139 
     | 
    
         
            +
            author: "Your Name"
         
     | 
| 
      
 140 
     | 
    
         
            +
            published_at: 2025-01-17
         
     | 
| 
      
 141 
     | 
    
         
            +
            tags:
         
     | 
| 
      
 142 
     | 
    
         
            +
              - tutorial
         
     | 
| 
      
 143 
     | 
    
         
            +
              - ro
         
     | 
| 
      
 144 
     | 
    
         
            +
            ```
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
            Create the asset directory and add files:
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 149 
     | 
    
         
            +
            mkdir posts/my-first-post
         
     | 
| 
      
 150 
     | 
    
         
            +
            echo "# Hello World" > posts/my-first-post/body.md
         
     | 
| 
      
 151 
     | 
    
         
            +
            cp ~/cover-image.jpg posts/my-first-post/cover.jpg
         
     | 
| 
      
 152 
     | 
    
         
            +
            ```
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
            Your structure should look like:
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
            ```
         
     | 
| 
      
 157 
     | 
    
         
            +
            my-ro/
         
     | 
| 
      
 158 
     | 
    
         
            +
            └── posts/
         
     | 
| 
      
 159 
     | 
    
         
            +
                ├── my-first-post.yml       ← Metadata
         
     | 
| 
      
 160 
     | 
    
         
            +
                └── my-first-post/          ← Assets
         
     | 
| 
      
 161 
     | 
    
         
            +
                    ├── body.md
         
     | 
| 
      
 162 
     | 
    
         
            +
                    └── cover.jpg
         
     | 
| 
      
 163 
     | 
    
         
            +
            ```
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
            ### Loading Assets with the ro Gem
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 168 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
            root = Ro::Root.new('./my-ro')
         
     | 
| 
      
 171 
     | 
    
         
            +
            posts = root.collection('posts')
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
            # Get a specific post
         
     | 
| 
      
 174 
     | 
    
         
            +
            post = posts.node_for('my-first-post')
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
            # Access metadata
         
     | 
| 
      
 177 
     | 
    
         
            +
            puts post[:title]           # => "My First Post"
         
     | 
| 
      
 178 
     | 
    
         
            +
            puts post[:author]          # => "Your Name"
         
     | 
| 
      
 179 
     | 
    
         
            +
            puts post.attributes        # => { title: "My First Post", ... }
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
            # Access assets
         
     | 
| 
      
 182 
     | 
    
         
            +
            post.asset_paths.each do |asset_path|
         
     | 
| 
      
 183 
     | 
    
         
            +
              puts asset_path  # => .../posts/my-first-post/body.md
         
     | 
| 
      
 184 
     | 
    
         
            +
                           # => .../posts/my-first-post/cover.jpg
         
     | 
| 
      
 185 
     | 
    
         
            +
            end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
            # Iterate all posts
         
     | 
| 
      
 188 
     | 
    
         
            +
            posts.each do |post|
         
     | 
| 
      
 189 
     | 
    
         
            +
              puts "#{post.id}: #{post[:title]}"
         
     | 
| 
      
 190 
     | 
    
         
            +
            end
         
     | 
| 
      
 191 
     | 
    
         
            +
            ```
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            ---
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
            ## Common Workflows
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
            ### Workflow 1: Adding a New Post (Programmatic)
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 200 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 201 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 202 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
            root = Ro::Root.new('./my-ro')
         
     | 
| 
      
 205 
     | 
    
         
            +
            posts = root.collection('posts')
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
            # Define new post ID and metadata
         
     | 
| 
      
 208 
     | 
    
         
            +
            post_id = 'my-new-post'
         
     | 
| 
      
 209 
     | 
    
         
            +
            metadata = {
         
     | 
| 
      
 210 
     | 
    
         
            +
              title: "My New Post",
         
     | 
| 
      
 211 
     | 
    
         
            +
              author: "Your Name",
         
     | 
| 
      
 212 
     | 
    
         
            +
              published_at: Date.today.to_s,
         
     | 
| 
      
 213 
     | 
    
         
            +
              tags: ['ruby', 'gems']
         
     | 
| 
      
 214 
     | 
    
         
            +
            }
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
            # Create metadata file
         
     | 
| 
      
 217 
     | 
    
         
            +
            metadata_file = posts.path / "#{post_id}.yml"
         
     | 
| 
      
 218 
     | 
    
         
            +
            File.write(metadata_file, metadata.to_yaml)
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
            # Create asset directory and add files
         
     | 
| 
      
 221 
     | 
    
         
            +
            asset_dir = posts.path / post_id
         
     | 
| 
      
 222 
     | 
    
         
            +
            FileUtils.mkdir_p(asset_dir)
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            body_content = "# My New Post\n\nThis is the content."
         
     | 
| 
      
 225 
     | 
    
         
            +
            File.write(asset_dir / 'body.md', body_content)
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
            # Verify
         
     | 
| 
      
 228 
     | 
    
         
            +
            node = posts.node_for(post_id)
         
     | 
| 
      
 229 
     | 
    
         
            +
            puts node[:title]  # => "My New Post"
         
     | 
| 
      
 230 
     | 
    
         
            +
            ```
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
            ### Workflow 2: Updating Post Metadata
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 235 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
            root = Ro::Root.new('./my-ro')
         
     | 
| 
      
 238 
     | 
    
         
            +
            post = root.collection('posts').node_for('my-first-post')
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
            # Update attributes
         
     | 
| 
      
 241 
     | 
    
         
            +
            post.update_attributes!(
         
     | 
| 
      
 242 
     | 
    
         
            +
              title: "Updated Title",
         
     | 
| 
      
 243 
     | 
    
         
            +
              tags: post[:tags] + ['updated']
         
     | 
| 
      
 244 
     | 
    
         
            +
            )
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
            # Reload to verify
         
     | 
| 
      
 247 
     | 
    
         
            +
            updated_post = root.collection('posts').node_for('my-first-post')
         
     | 
| 
      
 248 
     | 
    
         
            +
            puts updated_post[:title]  # => "Updated Title"
         
     | 
| 
      
 249 
     | 
    
         
            +
            ```
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
            ### Workflow 3: Adding Assets to Existing Post
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 254 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 255 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
            root = Ro::Root.new('./my-ro')
         
     | 
| 
      
 258 
     | 
    
         
            +
            post = root.collection('posts').node_for('my-first-post')
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
            # Copy a new image to the post's asset directory
         
     | 
| 
      
 261 
     | 
    
         
            +
            source_image = './new-diagram.png'
         
     | 
| 
      
 262 
     | 
    
         
            +
            dest_image = post.asset_dir / 'diagram.png'
         
     | 
| 
      
 263 
     | 
    
         
            +
            FileUtils.cp(source_image, dest_image)
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
            # Verify
         
     | 
| 
      
 266 
     | 
    
         
            +
            puts post.asset_paths.map(&:basename)  # => ["body.md", "cover.jpg", "diagram.png"]
         
     | 
| 
      
 267 
     | 
    
         
            +
            ```
         
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
      
 269 
     | 
    
         
            +
            ### Workflow 4: Listing All Posts with Assets
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 272 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
            root = Ro::Root.new('./my-ro')
         
     | 
| 
      
 275 
     | 
    
         
            +
            posts = root.collection('posts')
         
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
      
 277 
     | 
    
         
            +
            posts.each do |post|
         
     | 
| 
      
 278 
     | 
    
         
            +
              puts "Post: #{post[:title]}"
         
     | 
| 
      
 279 
     | 
    
         
            +
              puts "  ID: #{post.id}"
         
     | 
| 
      
 280 
     | 
    
         
            +
              puts "  Assets: #{post.asset_paths.size} files"
         
     | 
| 
      
 281 
     | 
    
         
            +
              post.asset_paths.each do |asset|
         
     | 
| 
      
 282 
     | 
    
         
            +
                puts "    - #{asset.basename}"
         
     | 
| 
      
 283 
     | 
    
         
            +
              end
         
     | 
| 
      
 284 
     | 
    
         
            +
              puts
         
     | 
| 
      
 285 
     | 
    
         
            +
            end
         
     | 
| 
      
 286 
     | 
    
         
            +
            ```
         
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
      
 288 
     | 
    
         
            +
            ---
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
            ## Integration Scenarios
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
            ### Scenario 1: Building a Static Site
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 295 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 296 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 297 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
            # Load ro data
         
     | 
| 
      
 300 
     | 
    
         
            +
            root = Ro::Root.new('./content')
         
     | 
| 
      
 301 
     | 
    
         
            +
            posts = root.collection('posts')
         
     | 
| 
      
 302 
     | 
    
         
            +
             
     | 
| 
      
 303 
     | 
    
         
            +
            # Build static JSON API
         
     | 
| 
      
 304 
     | 
    
         
            +
            api_dir = './public/api'
         
     | 
| 
      
 305 
     | 
    
         
            +
            FileUtils.mkdir_p(api_dir)
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
            # Generate index
         
     | 
| 
      
 308 
     | 
    
         
            +
            index = posts.map do |post|
         
     | 
| 
      
 309 
     | 
    
         
            +
              {
         
     | 
| 
      
 310 
     | 
    
         
            +
                id: post.id,
         
     | 
| 
      
 311 
     | 
    
         
            +
                title: post[:title],
         
     | 
| 
      
 312 
     | 
    
         
            +
                author: post[:author],
         
     | 
| 
      
 313 
     | 
    
         
            +
                published_at: post[:published_at],
         
     | 
| 
      
 314 
     | 
    
         
            +
                url: "/posts/#{post.id}"
         
     | 
| 
      
 315 
     | 
    
         
            +
              }
         
     | 
| 
      
 316 
     | 
    
         
            +
            end
         
     | 
| 
      
 317 
     | 
    
         
            +
            File.write("#{api_dir}/posts.json", JSON.pretty_generate(index))
         
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
      
 319 
     | 
    
         
            +
            # Generate individual post files
         
     | 
| 
      
 320 
     | 
    
         
            +
            posts.each do |post|
         
     | 
| 
      
 321 
     | 
    
         
            +
              post_data = {
         
     | 
| 
      
 322 
     | 
    
         
            +
                id: post.id,
         
     | 
| 
      
 323 
     | 
    
         
            +
                attributes: post.attributes,
         
     | 
| 
      
 324 
     | 
    
         
            +
                assets: post.asset_paths.map { |p| "/assets/#{post.id}/#{p.basename}" }
         
     | 
| 
      
 325 
     | 
    
         
            +
              }
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
      
 327 
     | 
    
         
            +
              post_dir = "#{api_dir}/posts"
         
     | 
| 
      
 328 
     | 
    
         
            +
              FileUtils.mkdir_p(post_dir)
         
     | 
| 
      
 329 
     | 
    
         
            +
              File.write("#{post_dir}/#{post.id}.json", JSON.pretty_generate(post_data))
         
     | 
| 
      
 330 
     | 
    
         
            +
             
     | 
| 
      
 331 
     | 
    
         
            +
              # Copy assets
         
     | 
| 
      
 332 
     | 
    
         
            +
              asset_dest_dir = "./public/assets/#{post.id}"
         
     | 
| 
      
 333 
     | 
    
         
            +
              FileUtils.mkdir_p(asset_dest_dir)
         
     | 
| 
      
 334 
     | 
    
         
            +
              post.asset_paths.each do |asset|
         
     | 
| 
      
 335 
     | 
    
         
            +
                FileUtils.cp(asset, asset_dest_dir / asset.basename)
         
     | 
| 
      
 336 
     | 
    
         
            +
              end
         
     | 
| 
      
 337 
     | 
    
         
            +
            end
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
            puts "Static site built in ./public"
         
     | 
| 
      
 340 
     | 
    
         
            +
            ```
         
     | 
| 
      
 341 
     | 
    
         
            +
             
     | 
| 
      
 342 
     | 
    
         
            +
            ### Scenario 2: Markdown Blog Integration
         
     | 
| 
      
 343 
     | 
    
         
            +
             
     | 
| 
      
 344 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 345 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 346 
     | 
    
         
            +
            require 'kramdown'
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
            root = Ro::Root.new('./blog')
         
     | 
| 
      
 349 
     | 
    
         
            +
            posts = root.collection('posts')
         
     | 
| 
      
 350 
     | 
    
         
            +
             
     | 
| 
      
 351 
     | 
    
         
            +
            # Render posts to HTML
         
     | 
| 
      
 352 
     | 
    
         
            +
            posts.each do |post|
         
     | 
| 
      
 353 
     | 
    
         
            +
              # Read markdown body
         
     | 
| 
      
 354 
     | 
    
         
            +
              body_path = post.asset_dir / 'body.md'
         
     | 
| 
      
 355 
     | 
    
         
            +
              next unless body_path.exist?
         
     | 
| 
      
 356 
     | 
    
         
            +
             
     | 
| 
      
 357 
     | 
    
         
            +
              markdown = File.read(body_path)
         
     | 
| 
      
 358 
     | 
    
         
            +
             
     | 
| 
      
 359 
     | 
    
         
            +
              # Render to HTML
         
     | 
| 
      
 360 
     | 
    
         
            +
              html = Kramdown::Document.new(markdown, input: 'GFM').to_html
         
     | 
| 
      
 361 
     | 
    
         
            +
             
     | 
| 
      
 362 
     | 
    
         
            +
              # Combine with metadata
         
     | 
| 
      
 363 
     | 
    
         
            +
              output = <<~HTML
         
     | 
| 
      
 364 
     | 
    
         
            +
                <!DOCTYPE html>
         
     | 
| 
      
 365 
     | 
    
         
            +
                <html>
         
     | 
| 
      
 366 
     | 
    
         
            +
                <head>
         
     | 
| 
      
 367 
     | 
    
         
            +
                  <title>#{post[:title]}</title>
         
     | 
| 
      
 368 
     | 
    
         
            +
                  <meta name="author" content="#{post[:author]}">
         
     | 
| 
      
 369 
     | 
    
         
            +
                </head>
         
     | 
| 
      
 370 
     | 
    
         
            +
                <body>
         
     | 
| 
      
 371 
     | 
    
         
            +
                  <h1>#{post[:title]}</h1>
         
     | 
| 
      
 372 
     | 
    
         
            +
                  <p>By #{post[:author]} on #{post[:published_at]}</p>
         
     | 
| 
      
 373 
     | 
    
         
            +
                  #{html}
         
     | 
| 
      
 374 
     | 
    
         
            +
                </body>
         
     | 
| 
      
 375 
     | 
    
         
            +
                </html>
         
     | 
| 
      
 376 
     | 
    
         
            +
              HTML
         
     | 
| 
      
 377 
     | 
    
         
            +
             
     | 
| 
      
 378 
     | 
    
         
            +
              # Write HTML file
         
     | 
| 
      
 379 
     | 
    
         
            +
              html_dir = './output/posts'
         
     | 
| 
      
 380 
     | 
    
         
            +
              FileUtils.mkdir_p(html_dir)
         
     | 
| 
      
 381 
     | 
    
         
            +
              File.write("#{html_dir}/#{post.id}.html", output)
         
     | 
| 
      
 382 
     | 
    
         
            +
            end
         
     | 
| 
      
 383 
     | 
    
         
            +
             
     | 
| 
      
 384 
     | 
    
         
            +
            puts "Blog rendered to ./output/posts"
         
     | 
| 
      
 385 
     | 
    
         
            +
            ```
         
     | 
| 
      
 386 
     | 
    
         
            +
             
     | 
| 
      
 387 
     | 
    
         
            +
            ### Scenario 3: API Server with Sinatra
         
     | 
| 
      
 388 
     | 
    
         
            +
             
     | 
| 
      
 389 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 390 
     | 
    
         
            +
            require 'sinatra'
         
     | 
| 
      
 391 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 392 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 393 
     | 
    
         
            +
             
     | 
| 
      
 394 
     | 
    
         
            +
            # Initialize ro
         
     | 
| 
      
 395 
     | 
    
         
            +
            set :ro_root, Ro::Root.new('./content')
         
     | 
| 
      
 396 
     | 
    
         
            +
             
     | 
| 
      
 397 
     | 
    
         
            +
            # List all posts
         
     | 
| 
      
 398 
     | 
    
         
            +
            get '/api/posts' do
         
     | 
| 
      
 399 
     | 
    
         
            +
              content_type :json
         
     | 
| 
      
 400 
     | 
    
         
            +
              posts = settings.ro_root.collection('posts')
         
     | 
| 
      
 401 
     | 
    
         
            +
             
     | 
| 
      
 402 
     | 
    
         
            +
              posts.map do |post|
         
     | 
| 
      
 403 
     | 
    
         
            +
                {
         
     | 
| 
      
 404 
     | 
    
         
            +
                  id: post.id,
         
     | 
| 
      
 405 
     | 
    
         
            +
                  title: post[:title],
         
     | 
| 
      
 406 
     | 
    
         
            +
                  author: post[:author],
         
     | 
| 
      
 407 
     | 
    
         
            +
                  url: "/api/posts/#{post.id}"
         
     | 
| 
      
 408 
     | 
    
         
            +
                }
         
     | 
| 
      
 409 
     | 
    
         
            +
              end.to_json
         
     | 
| 
      
 410 
     | 
    
         
            +
            end
         
     | 
| 
      
 411 
     | 
    
         
            +
             
     | 
| 
      
 412 
     | 
    
         
            +
            # Get single post
         
     | 
| 
      
 413 
     | 
    
         
            +
            get '/api/posts/:id' do
         
     | 
| 
      
 414 
     | 
    
         
            +
              content_type :json
         
     | 
| 
      
 415 
     | 
    
         
            +
              posts = settings.ro_root.collection('posts')
         
     | 
| 
      
 416 
     | 
    
         
            +
              post = posts.node_for(params[:id])
         
     | 
| 
      
 417 
     | 
    
         
            +
             
     | 
| 
      
 418 
     | 
    
         
            +
              halt 404, { error: 'Not found' }.to_json unless post
         
     | 
| 
      
 419 
     | 
    
         
            +
             
     | 
| 
      
 420 
     | 
    
         
            +
              {
         
     | 
| 
      
 421 
     | 
    
         
            +
                id: post.id,
         
     | 
| 
      
 422 
     | 
    
         
            +
                attributes: post.attributes,
         
     | 
| 
      
 423 
     | 
    
         
            +
                assets: post.asset_paths.map { |p| "/assets/#{post.id}/#{p.basename}" }
         
     | 
| 
      
 424 
     | 
    
         
            +
              }.to_json
         
     | 
| 
      
 425 
     | 
    
         
            +
            end
         
     | 
| 
      
 426 
     | 
    
         
            +
             
     | 
| 
      
 427 
     | 
    
         
            +
            # Serve assets
         
     | 
| 
      
 428 
     | 
    
         
            +
            get '/assets/:post_id/:filename' do
         
     | 
| 
      
 429 
     | 
    
         
            +
              posts = settings.ro_root.collection('posts')
         
     | 
| 
      
 430 
     | 
    
         
            +
              post = posts.node_for(params[:post_id])
         
     | 
| 
      
 431 
     | 
    
         
            +
             
     | 
| 
      
 432 
     | 
    
         
            +
              halt 404 unless post
         
     | 
| 
      
 433 
     | 
    
         
            +
             
     | 
| 
      
 434 
     | 
    
         
            +
              asset = post.asset_dir / params[:filename]
         
     | 
| 
      
 435 
     | 
    
         
            +
              halt 404 unless asset.exist?
         
     | 
| 
      
 436 
     | 
    
         
            +
             
     | 
| 
      
 437 
     | 
    
         
            +
              send_file asset
         
     | 
| 
      
 438 
     | 
    
         
            +
            end
         
     | 
| 
      
 439 
     | 
    
         
            +
            ```
         
     | 
| 
      
 440 
     | 
    
         
            +
             
     | 
| 
      
 441 
     | 
    
         
            +
            ---
         
     | 
| 
      
 442 
     | 
    
         
            +
             
     | 
| 
      
 443 
     | 
    
         
            +
            ## Troubleshooting
         
     | 
| 
      
 444 
     | 
    
         
            +
             
     | 
| 
      
 445 
     | 
    
         
            +
            ### Issue: "Metadata file not found"
         
     | 
| 
      
 446 
     | 
    
         
            +
             
     | 
| 
      
 447 
     | 
    
         
            +
            **Symptom**: `collection.node_for('my-post')` returns `nil`
         
     | 
| 
      
 448 
     | 
    
         
            +
             
     | 
| 
      
 449 
     | 
    
         
            +
            **Cause**: Metadata file doesn't exist or has wrong extension
         
     | 
| 
      
 450 
     | 
    
         
            +
             
     | 
| 
      
 451 
     | 
    
         
            +
            **Solution**:
         
     | 
| 
      
 452 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 453 
     | 
    
         
            +
            # Check if metadata file exists
         
     | 
| 
      
 454 
     | 
    
         
            +
            ls ./my-ro/posts/my-post.yml
         
     | 
| 
      
 455 
     | 
    
         
            +
             
     | 
| 
      
 456 
     | 
    
         
            +
            # If not, create it:
         
     | 
| 
      
 457 
     | 
    
         
            +
            cat > ./my-ro/posts/my-post.yml <<EOF
         
     | 
| 
      
 458 
     | 
    
         
            +
            title: "My Post"
         
     | 
| 
      
 459 
     | 
    
         
            +
            EOF
         
     | 
| 
      
 460 
     | 
    
         
            +
            ```
         
     | 
| 
      
 461 
     | 
    
         
            +
             
     | 
| 
      
 462 
     | 
    
         
            +
            ### Issue: "No assets found"
         
     | 
| 
      
 463 
     | 
    
         
            +
             
     | 
| 
      
 464 
     | 
    
         
            +
            **Symptom**: `node.asset_paths` is empty
         
     | 
| 
      
 465 
     | 
    
         
            +
             
     | 
| 
      
 466 
     | 
    
         
            +
            **Cause**: Asset directory doesn't exist
         
     | 
| 
      
 467 
     | 
    
         
            +
             
     | 
| 
      
 468 
     | 
    
         
            +
            **Solution**:
         
     | 
| 
      
 469 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 470 
     | 
    
         
            +
            # Create asset directory
         
     | 
| 
      
 471 
     | 
    
         
            +
            mkdir ./my-ro/posts/my-post
         
     | 
| 
      
 472 
     | 
    
         
            +
             
     | 
| 
      
 473 
     | 
    
         
            +
            # Add files
         
     | 
| 
      
 474 
     | 
    
         
            +
            echo "# Content" > ./my-ro/posts/my-post/body.md
         
     | 
| 
      
 475 
     | 
    
         
            +
            ```
         
     | 
| 
      
 476 
     | 
    
         
            +
             
     | 
| 
      
 477 
     | 
    
         
            +
            ### Issue: "Migration failed partway through"
         
     | 
| 
      
 478 
     | 
    
         
            +
             
     | 
| 
      
 479 
     | 
    
         
            +
            **Symptom**: Some nodes migrated, some didn't
         
     | 
| 
      
 480 
     | 
    
         
            +
             
     | 
| 
      
 481 
     | 
    
         
            +
            **Solution**:
         
     | 
| 
      
 482 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 483 
     | 
    
         
            +
            # Check migration log for errors
         
     | 
| 
      
 484 
     | 
    
         
            +
            cat ./migration.log
         
     | 
| 
      
 485 
     | 
    
         
            +
             
     | 
| 
      
 486 
     | 
    
         
            +
            # If safe, re-run migration (will skip already-migrated nodes)
         
     | 
| 
      
 487 
     | 
    
         
            +
            ro migrate ./public/ro --verbose
         
     | 
| 
      
 488 
     | 
    
         
            +
             
     | 
| 
      
 489 
     | 
    
         
            +
            # Or rollback to backup:
         
     | 
| 
      
 490 
     | 
    
         
            +
            ro migrate --rollback ./public/ro.backup.20250117-143022
         
     | 
| 
      
 491 
     | 
    
         
            +
            ```
         
     | 
| 
      
 492 
     | 
    
         
            +
             
     | 
| 
      
 493 
     | 
    
         
            +
            ### Issue: "Both old and new structures exist"
         
     | 
| 
      
 494 
     | 
    
         
            +
             
     | 
| 
      
 495 
     | 
    
         
            +
            **Symptom**: Migration warnings about duplicate structures
         
     | 
| 
      
 496 
     | 
    
         
            +
             
     | 
| 
      
 497 
     | 
    
         
            +
            **Solution**:
         
     | 
| 
      
 498 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 499 
     | 
    
         
            +
            # The new structure takes precedence in v5.0
         
     | 
| 
      
 500 
     | 
    
         
            +
            # Manually remove old structure if migration completed:
         
     | 
| 
      
 501 
     | 
    
         
            +
            rm -rf ./public/ro/posts/my-post/attributes.yml
         
     | 
| 
      
 502 
     | 
    
         
            +
            rm -rf ./public/ro/posts/my-post/assets/
         
     | 
| 
      
 503 
     | 
    
         
            +
            ```
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
      
 505 
     | 
    
         
            +
            ---
         
     | 
| 
      
 506 
     | 
    
         
            +
             
     | 
| 
      
 507 
     | 
    
         
            +
            ## Best Practices
         
     | 
| 
      
 508 
     | 
    
         
            +
             
     | 
| 
      
 509 
     | 
    
         
            +
            ### 1. Metadata Naming
         
     | 
| 
      
 510 
     | 
    
         
            +
             
     | 
| 
      
 511 
     | 
    
         
            +
            Use kebab-case for post IDs:
         
     | 
| 
      
 512 
     | 
    
         
            +
            - ✓ `my-first-post.yml`
         
     | 
| 
      
 513 
     | 
    
         
            +
            - ✗ `My First Post.yml` (spaces)
         
     | 
| 
      
 514 
     | 
    
         
            +
            - ✗ `my_first_post.yml` (underscores okay but less common)
         
     | 
| 
      
 515 
     | 
    
         
            +
             
     | 
| 
      
 516 
     | 
    
         
            +
            ### 2. Asset Organization
         
     | 
| 
      
 517 
     | 
    
         
            +
             
     | 
| 
      
 518 
     | 
    
         
            +
            Keep assets organized in subdirectories:
         
     | 
| 
      
 519 
     | 
    
         
            +
            ```
         
     | 
| 
      
 520 
     | 
    
         
            +
            my-post/
         
     | 
| 
      
 521 
     | 
    
         
            +
            ├── body.md
         
     | 
| 
      
 522 
     | 
    
         
            +
            ├── images/
         
     | 
| 
      
 523 
     | 
    
         
            +
            │   ├── hero.jpg
         
     | 
| 
      
 524 
     | 
    
         
            +
            │   └── diagram.png
         
     | 
| 
      
 525 
     | 
    
         
            +
            └── downloads/
         
     | 
| 
      
 526 
     | 
    
         
            +
                └── code-sample.zip
         
     | 
| 
      
 527 
     | 
    
         
            +
            ```
         
     | 
| 
      
 528 
     | 
    
         
            +
             
     | 
| 
      
 529 
     | 
    
         
            +
            ### 3. Metadata Format
         
     | 
| 
      
 530 
     | 
    
         
            +
             
     | 
| 
      
 531 
     | 
    
         
            +
            Prefer YAML for human-edited metadata:
         
     | 
| 
      
 532 
     | 
    
         
            +
            - YAML: Best for hand-editing (`.yml`)
         
     | 
| 
      
 533 
     | 
    
         
            +
            - JSON: Best for programmatic generation (`.json`)
         
     | 
| 
      
 534 
     | 
    
         
            +
            - TOML: Alternative (`.toml`, if supported)
         
     | 
| 
      
 535 
     | 
    
         
            +
             
     | 
| 
      
 536 
     | 
    
         
            +
            ### 4. Version Control
         
     | 
| 
      
 537 
     | 
    
         
            +
             
     | 
| 
      
 538 
     | 
    
         
            +
            In `.gitignore`, track metadata and markdown, ignore generated files:
         
     | 
| 
      
 539 
     | 
    
         
            +
            ```gitignore
         
     | 
| 
      
 540 
     | 
    
         
            +
            # Track these:
         
     | 
| 
      
 541 
     | 
    
         
            +
            # *.yml
         
     | 
| 
      
 542 
     | 
    
         
            +
            # *.md
         
     | 
| 
      
 543 
     | 
    
         
            +
             
     | 
| 
      
 544 
     | 
    
         
            +
            # Ignore these:
         
     | 
| 
      
 545 
     | 
    
         
            +
            public/api/
         
     | 
| 
      
 546 
     | 
    
         
            +
            .backup.*
         
     | 
| 
      
 547 
     | 
    
         
            +
            ```
         
     | 
| 
      
 548 
     | 
    
         
            +
             
     | 
| 
      
 549 
     | 
    
         
            +
            ### 5. Backup Before Migration
         
     | 
| 
      
 550 
     | 
    
         
            +
             
     | 
| 
      
 551 
     | 
    
         
            +
            Always create a backup before migrating:
         
     | 
| 
      
 552 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 553 
     | 
    
         
            +
            # Timestamp your backups
         
     | 
| 
      
 554 
     | 
    
         
            +
            cp -r ./ro "./ro.backup.$(date +%Y%m%d-%H%M%S)"
         
     | 
| 
      
 555 
     | 
    
         
            +
            ```
         
     | 
| 
      
 556 
     | 
    
         
            +
             
     | 
| 
      
 557 
     | 
    
         
            +
            ---
         
     | 
| 
      
 558 
     | 
    
         
            +
             
     | 
| 
      
 559 
     | 
    
         
            +
            ## What's Next?
         
     | 
| 
      
 560 
     | 
    
         
            +
             
     | 
| 
      
 561 
     | 
    
         
            +
            - **Spec**: Read [spec.md](./spec.md) for full feature requirements
         
     | 
| 
      
 562 
     | 
    
         
            +
            - **Implementation Plan**: See [plan.md](./plan.md) for technical architecture
         
     | 
| 
      
 563 
     | 
    
         
            +
            - **Data Model**: Review [data-model.md](./data-model.md) for entity relationships
         
     | 
| 
      
 564 
     | 
    
         
            +
            - **API Contracts**: Check [contracts/](./contracts/) for detailed API documentation
         
     | 
| 
      
 565 
     | 
    
         
            +
            - **Tasks**: Execute [tasks.md](./tasks.md) for implementation checklist (after running `/speckit.tasks`)
         
     | 
| 
      
 566 
     | 
    
         
            +
             
     | 
| 
      
 567 
     | 
    
         
            +
            ---
         
     | 
| 
      
 568 
     | 
    
         
            +
             
     | 
| 
      
 569 
     | 
    
         
            +
            ## Version Info
         
     | 
| 
      
 570 
     | 
    
         
            +
             
     | 
| 
      
 571 
     | 
    
         
            +
            **Feature Version**: 5.0.0 (breaking change from 4.x)
         
     | 
| 
      
 572 
     | 
    
         
            +
            **Migration Required**: Yes (one-time, run `ro migrate`)
         
     | 
| 
      
 573 
     | 
    
         
            +
            **Backward Compatibility**: No (major version bump)
         
     | 
| 
      
 574 
     | 
    
         
            +
             
     | 
| 
      
 575 
     | 
    
         
            +
            For questions or issues, refer to the main ro gem documentation or file an issue on GitHub.
         
     |