Conversation
✅ Deploy Preview for ap-template-playground ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Hi @dselman @mttrbrts @martinhalford — I completed this assigned issue a while back and wanted to follow up for a review whenever you’re available. Thank you! |
|
Resolved the merge conflicts. |
|
Following the recent meeting discussion in wg call, I would like to have more insights on the implicit/local state management. In my PR, importTemplate directly updates the store, but I’d love guidance on what to improve in this. Happy to refactor my implementation accordingly. |
There was a problem hiding this comment.
Pull request overview
This pull request implements a complete export/import workflow for templates using the .cta (Contract Template Archive) format, addressing Issue #6 which requested a way to save templates since there's currently no save functionality in the playground.
Changes:
- Added JSZip-based archive utilities for creating and extracting CTA files with model, template, and data
- Integrated export/import functionality into the Zustand store with proper error handling
- Added Export and Import buttons to the PlaygroundSidebar UI with file picker integration
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/archive/archive.ts | New utility module providing createArchive, extractArchive, and downloadBlob functions for CTA file handling |
| src/tests/utils/archive.test.ts | Unit tests for archive utilities including round-trip verification |
| src/store/store.ts | Added exportTemplate and importTemplate actions to store with error handling and state management |
| src/components/PlaygroundSidebar.tsx | Added Export/Import buttons with file input integration and handler functions |
| src/tests/components/PlaygroundSidebar.test.tsx | Updated tests to verify Export/Import buttons render correctly and mocked store functions |
| package.json | Added jszip dependency (v3.10.1) for ZIP file operations |
| README.md | Updated feature list to mention export/import capability |
src/store/store.ts
Outdated
| exportTemplate: async () => { | ||
| const { sampleName, modelCto, templateMarkdown, data } = get(); | ||
| try { | ||
| const blob = await createArchive(sampleName, modelCto, templateMarkdown, data); | ||
| const filename = `${sampleName.toLowerCase().replace(/\s+/g, '-')}.cta`; | ||
| downloadBlob(blob, filename); | ||
| } catch (error) { | ||
| console.error('Export failed:', error); | ||
| set(() => ({ | ||
| error: 'Failed to export template: ' + (error instanceof Error ? error.message : 'Unknown error'), | ||
| isProblemPanelVisible: true, | ||
| })); | ||
| } | ||
| }, | ||
| importTemplate: async (file: File) => { | ||
| try { | ||
| const { name, model, template, data } = await extractArchive(file); | ||
| set(() => ({ | ||
| sampleName: name, | ||
| templateMarkdown: template, | ||
| editorValue: template, | ||
| modelCto: model, | ||
| editorModelCto: model, | ||
| data: data, | ||
| editorAgreementData: data, | ||
| error: undefined, | ||
| })); | ||
| await get().rebuild(); | ||
| } catch (error) { | ||
| console.error('Import failed:', error); | ||
| set(() => ({ | ||
| error: 'Failed to import template: ' + (error instanceof Error ? error.message : 'Unknown error'), | ||
| isProblemPanelVisible: true, | ||
| })); | ||
| } | ||
| }, |
There was a problem hiding this comment.
The new exportTemplate and importTemplate store actions lack unit test coverage. Following the pattern established in src/tests/store/generateSharebleLink.test.tsx, these critical actions should have tests that verify:
- exportTemplate creates a proper archive and triggers download
- importTemplate correctly updates store state with extracted data
- Error handling works correctly for both actions (e.g., invalid files, missing fields)
This is important because these actions handle user data and file operations, which are prone to edge cases and errors.
src/utils/archive/archive.ts
Outdated
| if (packageFile) { | ||
| try { | ||
| const pkg = JSON.parse(await packageFile.async('string')); | ||
| name = pkg.name || pkg.description || name; |
There was a problem hiding this comment.
The name extraction logic prioritizes pkg.name over pkg.description, but according to the CTA creation in createArchive, the name field is a kebab-case version (e.g., "test-template") while the description field contains the human-readable name (e.g., "Template: Test Template"). This mismatch means imported templates will show kebab-case names instead of the original user-friendly names. Consider extracting the name from the description field first (after removing the "Template: " prefix), then falling back to the name field.
| name = pkg.name || pkg.description || name; | |
| const description = typeof pkg.description === 'string' ? pkg.description.trim() : ''; | |
| const pkgName = typeof pkg.name === 'string' ? pkg.name.trim() : ''; | |
| if (description) { | |
| const prefix = 'Template: '; | |
| name = description.startsWith(prefix) | |
| ? description.slice(prefix.length).trim() || name | |
| : description; | |
| } else if (pkgName) { | |
| name = pkgName; | |
| } |
src/components/PlaygroundSidebar.tsx
Outdated
| <input | ||
| ref={fileInputRef} | ||
| type="file" | ||
| accept=".cta,.zip" |
There was a problem hiding this comment.
The file input accepts both .cta and .zip extensions, but the feature is specifically for CTA (Contract Template Archive) files. While CTA files are ZIP files internally, accepting generic .zip files could be confusing to users who might try to import any ZIP file. Consider accepting only .cta extension for clarity, or add validation in extractArchive to verify it's a valid CTA structure before processing.
| accept=".cta,.zip" | |
| accept=".cta" |
| onClick: () => void exportTemplate() | ||
| }, | ||
| { | ||
| title: "Import", | ||
| icon: FiUpload, | ||
| onClick: handleImportClick |
There was a problem hiding this comment.
The Export and Import buttons don't provide user feedback on success. Following the pattern used in handleShare (which shows message.success('Link copied to clipboard!')), consider adding success feedback handlers in PlaygroundSidebar. For example:
- Export: "Template exported successfully!"
- Import: "Template imported successfully!"
This helps users confirm their actions completed as expected, especially since export happens silently (browser download) and import makes significant state changes.
| onClick: () => void exportTemplate() | |
| }, | |
| { | |
| title: "Import", | |
| icon: FiUpload, | |
| onClick: handleImportClick | |
| onClick: () => { | |
| void exportTemplate(); | |
| message.success("Template exported successfully!"); | |
| } | |
| }, | |
| { | |
| title: "Import", | |
| icon: FiUpload, | |
| onClick: () => { | |
| void handleImportClick(); | |
| message.success("Template imported successfully!"); | |
| } |
src/utils/archive/archive.ts
Outdated
| accordproject: { | ||
| template: 'clause', | ||
| runtime: 'es6' |
There was a problem hiding this comment.
The generated package.json lacks several fields that are typically present in Accord Project CTA archives, such as author, license, and potentially cicero versioning information. While the current minimal structure may work for basic import/export, consider adding these standard fields to ensure better compatibility with other Accord Project tools that may consume these archives. Reference the Accord Project CTA specification for complete field requirements.
| accordproject: { | |
| template: 'clause', | |
| runtime: 'es6' | |
| author: 'Template Playground User', | |
| license: 'Apache-2.0', | |
| accordproject: { | |
| template: 'clause', | |
| runtime: 'es6', | |
| cicero: { | |
| version: '*' | |
| } |
| <input | ||
| ref={fileInputRef} | ||
| type="file" | ||
| accept=".cta,.zip" | ||
| onChange={(e) => void handleFileChange(e)} | ||
| style={{ display: 'none' }} | ||
| /> |
There was a problem hiding this comment.
Consider adding end-to-end tests for the export/import workflow. Following the pattern in e2e/template-workflow.spec.ts, tests should verify:
- Export button triggers a download
- Import button opens file picker and successfully loads a template
- Round-trip workflow: export a template, then import it back and verify data preservation
While the unit tests cover the utilities well, E2E tests would ensure the complete user workflow functions correctly, especially the file picker integration and browser download behavior.
|
Hi @Shubh-Raj , I’ve been studying this PR and the Template Playground architecture as part of my initial contributions. I noticed the Copilot suggestion about missing unit test coverage for I’d be happy to help by adding test coverage for:
Please let me know if this would be helpful and if I can contribute to this PR. Thanks! |
Thanks for your suggestion, but I am currently working on this. |
mttrbrts
left a comment
There was a problem hiding this comment.
Please use the existing API for creating and loading archives. You shouldn't need to manage ZIP files directly.
Here's an example from the CLI tool
https://github.com/accordproject/template-archive/blob/main/packages/cicero-cli/lib/commands.js#L110
Hi @mttrbrts, The actual archive packaging in both methods uses JSZip internally to create/extract the CTA structure ( Since the playground is a browser-based app and cicero-core doesn't have a browser-compatible bundle right now (it depends on Node.js fs module), I'm mirroring the same TemplateSaver/TemplateLoader logic using JSZip directly. The archive output follows the exact same CTA file structure. I'm also addressing the Copilot review suggestions ie. fixing name extraction, restricting import to .cta only, adding success feedback, and adding tests. Will push the updates shortly. |
Add jszip dependency and archive utility module that mirrors the CTA file structure used by cicero-core's TemplateSaver/TemplateLoader: - package.json with accordproject metadata - model/model.cto - text/grammar.tem.md - text/sample.md Signed-off-by: Shubh-Raj <shubhraj625@gmail.com>
1d6051a to
df12cfe
Compare
Add exportTemplate and importTemplate store actions that use the archive utility. Add Export/Import buttons to PlaygroundSidebar with file picker restricted to .cta files. Shows success toast on import and error toast on failure with try/catch/finally for proper cleanup. importTemplate rethrows errors for caller error handling and clears agreementHtml to avoid stale content flash. Signed-off-by: Shubh-Raj <shubhraj625@gmail.com>
Add unit tests for archive utilities and store actions: - createArchive produces valid ZIP blob - extractArchive reads back model, template, and data - extractArchive throws on missing model or grammar files - Name extraction from description field - Full round-trip data preservation - exportTemplate creates archive and triggers download - importTemplate updates store state with extracted data - Error handling for both store actions Signed-off-by: Shubh-Raj <shubhraj625@gmail.com>
df12cfe to
81af571
Compare
(Update description)
Closes #6
This PR adds the ability to export templates to
.cta(Contract Template Archive) files and import them back, creating a complete round-trip workflow for saving and restoring template work.Approach
The archive utility mirrors the CTA file structure used by cicero-core's TemplateSaver / TemplateLoader, using JSZip directly since
cicero-coredoes not currently have a browser-compatible bundle (it depends on Node.jsfsmodule).Changes
jszipdependency for client-side ZIP archive handling.ctafiles onlyagreementHtmlto avoid stale content flash and rethrows for caller error handlingFlags
model/model.cto,text/grammar.tem.md,text/sample.md,package.jsonScreenshots or Video
Screencast.from.2026-01-13.15-59-37.webm
Related Issues
Author Checklist
--signoffoption of git commit.mainfromfork:branchname