Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions apps/editor/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ function AppInner() {
// Handler to save the CFPackage to the server
// Must be defined before early returns (React hooks rules)
const handleSaveToServer = useCallback(
async (openCasePackage: unknown) => {
async (openCasePackage: unknown, framework: Framework) => {
if (!tenantId) {
throw new Error('Not signed in to a tenant. Please sign in to save.')
}
Expand Down Expand Up @@ -568,13 +568,20 @@ function AppInner() {
const { [oldId]: _dropped, ...rest } = prev
return rest
})
} else if (activeFrameworkId && result.isModifiedFromSource !== undefined) {
} else if (activeFrameworkId) {
// Refresh the local cache with the just-saved Framework (items, associations,
// metadata) so a hard refresh reflects the server state instead of the
// pre-save snapshot. Without this, edits made and saved in this session
// (e.g. a newly added item) would vanish on F5 until the framework was
// reopened from the Home screen, which always re-fetches from the server.
setFrameworks((prev) => {
const next = prev.map((f) =>
f.id === activeFrameworkId
? { ...f, mirrorStatus: { isModifiedFromSource: result.isModifiedFromSource, sourcePackageURI: result.sourcePackageURI } }
: f
)
const existingIdx = prev.findIndex((f) => f.id === activeFrameworkId)
if (existingIdx < 0) return prev
const mirrorStatus = result.isModifiedFromSource !== undefined
? { isModifiedFromSource: result.isModifiedFromSource, sourcePackageURI: result.sourcePackageURI }
: prev[existingIdx].mirrorStatus
const next = [...prev]
next[existingIdx] = createHomeFrameworkFromDomain(framework, mirrorStatus)
saveFrameworks(next)
return next
})
Expand Down
2 changes: 1 addition & 1 deletion apps/editor/src/application/framework/commands/AddItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type AddItem = Command<
frameworkId: FrameworkId
itemId: ItemId
statement: string
type: ItemType
type?: ItemType
}
>

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type { Framework, FrameworkMetadata, Item, Association, AssociationType,
import type { FrameworkId, ItemId, AssociationId } from '@/domain/shared/types'
import { normalizeAdoptionStatus } from '@/domain/framework/model/adoptionStatus'

function mapItemType(rawType?: string): ItemType {
function mapItemType(rawType?: string): ItemType | undefined {
const raw = (rawType ?? '').toLowerCase()
if (raw.includes('skill')) return 'Skill'
if (raw.includes('learning') || raw.includes('outcome')) return 'LearningOutcome'
if (raw.includes('standard')) return 'Standard'
if (raw.includes('compet')) return 'Competency'
return 'Competency'
return undefined
}

function mapAssociationType(rawType?: string): AssociationType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ describe('SpreadsheetToFramework', () => {
makeRow({ level: 1, fullStatement: 'A', itemType: 'competency' }),
makeRow({ level: 1, fullStatement: 'B', itemType: 'LEARNING OUTCOME' }),
makeRow({ level: 1, fullStatement: 'C', itemType: 'skill' }),
makeRow({ level: 1, fullStatement: 'D' }), // defaults to Standard
makeRow({ level: 1, fullStatement: 'D' }), // no type entered — should remain unset
]
const fw = spreadsheetToFramework(rows, { title: 'Test' })
const items = [...fw.items.values()]

expect(items[0].type).toBe('Competency')
expect(items[1].type).toBe('LearningOutcome')
expect(items[2].type).toBe('Skill')
expect(items[3].type).toBe('Standard')
expect(items[3].type).toBeUndefined()
})

it('handles a single item (flat framework)', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const VALID_ITEM_TYPES: Record<string, ItemType> = {
skill: 'Skill',
}

function normaliseItemType(raw: string | undefined): ItemType {
if (!raw) return 'Standard'
return VALID_ITEM_TYPES[raw.trim().toLowerCase()] ?? 'Standard'
function normaliseItemType(raw: string | undefined): ItemType | undefined {
if (!raw) return undefined
return VALID_ITEM_TYPES[raw.trim().toLowerCase()] ?? undefined
}

// ── Public API ──────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion apps/editor/src/domain/framework/model/Item.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Item, ItemType } from './types'
import type { ItemId } from '@/domain/shared/types'

export function createItem(params: { id: ItemId; statement: string; type: ItemType }): Item {
export function createItem(params: { id: ItemId; statement: string; type?: ItemType }): Item {
return {
id: params.id,
statement: params.statement,
Expand Down
2 changes: 1 addition & 1 deletion apps/editor/src/domain/framework/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export type AssociationMetadata = {
export type Item = {
id: ItemId
statement: string
type: ItemType
type?: ItemType
metadata?: ItemMetadata
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import type { CaseApiClient } from './CaseApiClient'
export class CaseFrameworkRepository implements FrameworkRepository {
constructor(private readonly _client: CaseApiClient) {}

private mapItemType(cfItem: CFItem): ItemType {
private mapItemType(cfItem: CFItem): ItemType | undefined {
const raw = (cfItem.CFItemType ?? '').toLowerCase()
if (raw.includes('skill')) return 'Skill'
if (raw.includes('learning') || raw.includes('outcome')) return 'LearningOutcome'
if (raw.includes('standard')) return 'Standard'
if (raw.includes('compet')) return 'Competency'
return 'Competency'
return undefined
}

private mapAssociationType(assoc: CFAssociation): AssociationType {
Expand Down
4 changes: 2 additions & 2 deletions apps/editor/src/ui/editor/EditorCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type MirrorStatus = { isModifiedFromSource?: boolean; sourcePackageURI?: string

type EditorCanvasProps = {
onBack?: () => void
onSaveToServer?: (cfPackage: ReturnType<typeof toOpenCaseFormat>) => Promise<void>
onSaveToServer?: (cfPackage: ReturnType<typeof toOpenCaseFormat>, framework: Framework) => Promise<void>
/** Whether the current framework has been published to OpenCASE (loaded from or saved to server) */
isPublishedToOpenCase?: boolean
/** Archive the current framework on the server and navigate home */
Expand Down Expand Up @@ -215,7 +215,7 @@ export default function EditorCanvas({ onBack, onSaveToServer, isPublishedToOpen
setSaveStatus('saving')
setSaveError(null)
try {
await onSaveToServer(openCasePackage)
await onSaveToServer(openCasePackage, framework)
baselineFrameworkRef.current = framework
setSaveStatus('success')
clearDirty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import type { CFDocument, CFItem } from '@/domain/case/types'
const isFrameworkNode = (n: EditorGraph['nodes'][number]) => n.type === 'caseFrameworkNode'
const isItemNode = (n: EditorGraph['nodes'][number]) => n.type === 'caseItemNode'

function mapItemType(rawType?: string): ItemType {
function mapItemType(rawType?: string): ItemType | undefined {
const raw = (rawType ?? '').toLowerCase()
if (raw.includes('skill')) return 'Skill'
if (raw.includes('learning') || raw.includes('outcome')) return 'LearningOutcome'
if (raw.includes('standard')) return 'Standard'
if (raw.includes('compet')) return 'Competency'
return 'Competency'
return undefined
}

function edgeToAssociationType(edgeId: string, edgeData?: { associationType?: string; cfAssociation?: { associationType?: string } }): AssociationType {
Expand Down
Loading