Dictionary design for when you want class and property name to be the same?

I often want to name a property with the same name as the properties class. This seems to be causing problems.

For example in my dictionary I have classes:

selection
range

I also want to add a selection property to the window class through a class extension, and I want to add a range property to the selection class.

This doesn’t seem to work. I can only make things work when I rename so that classes and properties don’t share names.

My questions:

  1. Is this just a limitation of AppleScript? Or maybe there’s a way to make it work and I’m just messing something up in my .sdef file?

  2. If it is a AppleScript limitation, are there any good patterns to avoid the problem. For example I have been renaming the properties from selection to window selection and from range to selection range. Is that right? It works, but makes all the property names pretty long and cumbersome to type out.

    I’m also considering using srange instead of selection range and wselection instead of window selection, but not sure if that has a good AppleScript feel.

Anyway, please share any thoughts you have on these issues.

And I don’t know what I’m talking about. It seems to be working for me now. I swear over the last few days I keep running into problems where the solution was to rename properties and classes into different names… but now I have got things working with the particular case that I was just looking at, so doesn’t seem to be a general limitation.

Update Please do let me know if there are any limitations on this, or issues that I should be aware of.

Don’t. Ambiguous keywords just create pain for users. e.g.:

tell application "Some.app"
    set myVar to selection
end tell

If selection is a type name and a property name, what will the value of myVar be? And if it isn’t the one that you want, how can you get the other?

If the type and property names naturally want to be the same, and the object exists as a one-to-one relationship with its container, then I suggest you name the property selection and the type selection object. e.g. Finder follows this naming convention to distinguish the desktop property from the desktop object’s type.

(Finder also stupidly hyphenates the type name as desktop-object, which will mis-parse if you type it in a script. Don’t hyphenate names; stick to ASCII letters and spaces.)

If you are describing a one-to-many relationship then you generally don’t need to worry about property names. The singular and plural type names are used to describe elements of a container; e.g. selection 1 of anObject, selections 2 thru -1 of anObject. One exception is where there is a secondary one-to-one relationship, e.g. TextEdit has a document[s] type name, which it uses to represent document elements of application, but also uses document as a property name on its window class. In that situation, I’d have given the (rarely-used) property a longer name to distinguish it.

A good naming rule of thumb: choose names that are practical, intuitive, unambiguous, and won’t confuse either users or the AppleScript parser or pretty printer. Try to avoid English words whose singular and plural are the same (e.g. the dread text). Also, if you’re making one name longer than the other, consider which is used most often and make it the shorter if it is logical to do so.

Also, avoid conflicting with any of AppleScript’s built-in keywords (or keywords imported from Standard Additions). AppleScript does define a number of standard keywords which it doesn’t use itself but intends for scriptable applications to reuse when naming generic features: document[s], window[s], word[s], paragraph[s], name, id, etc. Reuse those where it’s logical (make sure you reuse the same four-char-codes too).

One last tip: write out some mock scripts using your proposed syntax before you do the hard work of actually implementing it. If your mockups look crap then try something else until you find a syntax that reads and writes naturally. Good Apple event interface design is good UI/UX design. You make it good by doing; trying out a range of ideas, discarding the bad, keeping the good. And then write the documentation for your dictionary; and if you struggle to explain a feature clearly and concisely then redesign it till you can.

Then implement all the guts, once you’re happy with that, but that’s a whole ’nother battle.

2 Likes

p.s. This close to WWDC 2022 I would also suggest you park any AppleScript development until after June 10th. No sense putting in hours in May if next month Apple announce that they’re finally pulling its plug, or even just make clear it has no long-term future. Once Apple [fail to] provide clarity [yet again], you can then decide if your projected ROI is worth the investment.

Thanks, that’s what I needed to hear. They seemed to be creating problems for me, but then sometimes I could make them work. Wasn’t sure if maybe I just wasn’t doing things right, but a nice hard rule like this is easy to follow :).

For my case what I’ve ended up doing is just got rid of the separate selection object and instead have placed those selection properties directly on window. It seems to be working pretty well. For example to get selected range property now I just deal with window like this:

tell front window of app "Bike"
    selected range
end tell

Great idea, thanks.

Here’s a script that I’ve been working on. My app is a outliner. This script is used to generate a new “today entry”. It works by creating a date hierarchy outline (year, month, day) and then inserts a new line in today’s day where you can start typing.

It seems “ok” to me, but took me a long time to find various short forms for things I’m trying to do. Probably still room for improvement. If you see anything obvious improvements for script or scripting dictionary please let me know.

set yearName to do shell script "date +'%Y'"
set yearId to "year-" & (do shell script "date +'%Y'")
set monthName to do shell script "date +'%B, %Y'"
set monthId to "month-" & (do shell script "date +'%Y%m'")
set dayName to do shell script "date +'%B %-d, %Y'"
set dayId to "day-" & (do shell script "date +'%Y%m%d'")

tell application "Bike"
	set doc to front document
	tell my makePath(doc, {{yearId, yearName}, {monthId, monthName}, {dayId, dayName}})
		select (make child at front)
	end tell
	export root of doc as Bike Format
end tell

to makePath(doc, pathItems)
	tell application "Bike"
		set container to root of doc
		repeat with segment in pathItems
			set segmentId to item 1 of segment
			set segmentName to item 2 of segment
			if exists row id segmentId of doc then
				set container to row id segmentId of doc
			else
				tell container
					set container to make child at front with properties {id:segmentId, name:segmentName}
				end tell
			end if
		end repeat
		return container
	end tell
end makePath

That would probably be smart thing to do, but when I’m not feeling super frustrated with AppleScript I do have fun with it. I’ve added it to other apps, so I’m not starting from zero, but I figured I would try to do with a little more understanding this time, thus my questions.

My app will eventually have a more fundamental JavaScript based automation layer, but building this AppleScript layer is a easier to start with. I want some sort of scripting for my 1.0 release, so I’m going to keep on with it no matter Apple’s plans.

If a selection has no attributes of interest and is a simple atomic view into another section of data, the selection property could just return a by-range reference to the selected elements (or insertion point for a positioned cursor).

document
    selection : reference -- the currently selected text, or insertion point

e.g.

tell document 1
    get selection
end tell
--> text (character 2) thru (character 8) of document 1 of app "Some.app"

tell document 1
    get contents of selection
end tell
--> "ello wo"

tell document 1
    set selection to text 1 thru -2
end tell

tell document 1
    set selection to beginning of paragraph 2
end tell

If you wish to make the selection UI more granular, e.g. so the user can get/set the start stop indexes on a range, or determine if the reference is a by-range reference or an insertion location, that’s when you’d make it a discrete object with properties and/or elements of its own, e.g.

document
    selection : selection object -- the currently selected text, or insertion point

selection object
    start : reference -- the start of the currently selected text, or insertion point for new text
    stop : reference | missing value -- the end of the currently selected text, or missing value if start is an insertion point
    contents : text -- the currently selected text

Honestly, there’s all sorts of ways you can present an app’s user-facing data. You could even use coercions to change how the selection appears to your script; e.g. selection as text (the logical default and what the user most commonly wants) vs selection as selection object (a deeper, more granular view). For building a UI it is highly flexible (too flexible, really) and that UI can be as high-level, “user-friendly”, and “intuitive” as you want to make it and are prepared to code.†

(One thing I would not do is move properties like start selection and stop selection onto the parent object itself. Sure it’ll work, but it’s ugly and clumsy and just bad UX. e.g. InDesign is famously bad for repeating dozens of properties on numerous class instead of factoring them out to a few self-contained object/record types, each of which encapsulates a single concept/feature. ISTR once calculating that ID’s monster dictionary could’ve been 30–40% smaller had they factored out stuff like text attributes this way.)

† This is one reason Carbon apps’ UX varied so much, since each rolled its Apple Event Object Model support largely from scratch. Mac OS X’s Cocoa Scripting framework encouraged some much needed standardization of basic behaviors, though unsophisticated, buggy, and tediously bureaucratic. (CS was designed by an OO programmer, and it shows.) BTW, when CS insists you do things a certain way, even though its way makes for a sucky UX, accept it. If you fight against it you’ll only lose, as Mark will attest.

1 Like

This code is deliberately naive; of course you’re not going to hardcode literals throughout a production script, and you’ll probably use assignments, flow control, and so on. But A. it reflects how the average AppleScripter will construct their application automation code (task-oriented; not professional developer); and B. it clearly describes how the script interacts with your app’s Apple event UI, free of clutter and distraction.

(Honestly, I struggled just to see what your script was trying to achieve under all the looping, handler-calling fanciness, further hindered by single-word variable names such as container which are visually indistinguishable from AppleScript/application keywords. Simple solution: get rid of all that! It’s not important to what you’re trying to do: see if your app’s UI is good and/or sucks.)

Looking at my cleaned up mock code, I see several things:

  1. root and child are terrible names. Generic, meaningless, completely focused on the wrong concept. You’re thinking like a programmer, not a user; in terms of abstract CS data structures, not the user’s actual stuff. What do you call an entry in an outliner when you’re explaining to the user how to word its GUI? Even entry would be a better name than child. That tells you what it is, not how it works. How it works is already explained by the relationships it has to other objects, as described in the app’s dictionary; in this case, the entry class will contain entry elements. Or, perhaps outline would be more appropriate jargon than entry?

  2. The root property—is this really necessary? Is there a UX reason not to make entry appear as elements on document? (Yes, I know CS tends to shoehorn apps into this stupid pattern; witness the text property on TextEdit’s document class, whereas pre-CS apps just represented the document’s text as character, word, paragraph elements on document itself. Even so, CS allows you to set a special contents attribute that short-circuits that property and reference its elements directly, e.g. paragraph 1 of document 1 instead of paragraph 1 of text of document 1).

As a user of an outliner app, I would assume that the open document is the root of the outline. That’s the logical mental model from the user’s POV, so replicate that if you can [within constraints of CS’s idiot views]. Thus the document class has entry elements, and the entry class also has entry elements.

  1. The make commands specify front [of container] as the insertion location (at parameter) for each new entry. If this is the intuitive [to the user] location at which to create new entries, and where your GUI places them by default because that’s where users most commonly expect and want new entries to appear, then make the at parameter optional with front [of container] as its default.

  2. Have a rummage around Finder for inspiration. Its dictionary is by no means perfect, but it does a decent presentation of a recursive containment model, complete with convenience shortcuts to individual nodes of particular practical interest to users (e.g. home and selection properties).

Finally, with all that said, I would still spend the next month getting up to speed on Node/V8 or JavaScriptCore, rather than writing Cocoa Scripting code. Playing around with UI/UX design is a productive exercise whichever way you eventually go (of course, a JS API should be implemented as a traditional DOM as that’s what JS users expect; but an overall look and feel is pretty generalizable). And JS is a huge, lively, growing market whereas AS is not, so it makes sense to prioritize a JS API for new product development simply in terms of “What will attract the most customers to my product?”

I can’t speak for V8’s embedding API beyond “C++, euwww”. If you want to embrace Node, I’d suggest looking at out-of-process *nix-style hosting of user scripts, with some popular established protocol + existing RPC library off npm to communicate between it and your app. I dunno what’s recommended there. But Node is Node, and it is very difficult to ignore.

As for JSCore, its ObjC embedding API is pretty easy to use (although nowadays you should be running it in XPC), and if you rummage around perhaps someone’s has done a native Swift wrapper by now. (I’m assuming your new app is in Swift, if only for sake of forward compatibility with Apple’s developer ecosystem.) The biggest problem JSCore has is that Apple utterly botched positioning it as a viable Node alternative, so while both are ES6 only one comes with npm support out of the box, and that alone (for all npm’s faults) is probably the killer USP that will seriously crimp JSCore’s competitiveness as a scripting platform.

One last thing re. Cocoa Scripting: CS is very coupled to AppKit’s application+document model, so the more you lean into Swift/SwiftUI, which is obviously where The Future lie now, the more painful I suspect CS becomes. Steve Troughton-Smith has done a little bit in this area; worth checking out to see what his pain points and opinions are.

1 Like

Thanks again for your thoughtful reply and AppleScript Dictionary suggestions. Going to take me some time to work through, but given me good things to think about.

I have used JavaScriptCore for embedded scripting in some of my other apps. I agree it has some definite advantages over AppleScript, and I expect it to eventually be the more powerful scripting layer in my new app too.

But I’m time constrained right now and would rather write a basic AppleScript layer right now (So my 1.0 release can be automated). Later I will add a more complex/powerful plugin/scripting layer that uses JavaScriptCore.

I’m looking for good AppleScript terms for addressing a hierarchy.

@hhas01 recommended against parent/children terminology, I’m looking for more standard alternatives.

My basic “item” is called a row, I think that makes sense to user. It directly contains a list of “children” rows. It also indirectly contains a list of “descendant” rows. I would like model both collections as elements of the row. Any suggestions?

OmniOutliner is the only app that I’ve noticed that has a similar structure, and it does use the term “children”, so maybe there’s precedent to follow in that case? Of course most AppleScript apps are modeling a hierarchy, but I can’t find any others (besides OmniOutliner, probably haven’t looked hard enough :)) that make a distinction between addressing direct children and all descendants.

I think children and descendants are very familiar and clear in the context of an outline structure, and, as you say, they build on the precedent of OmniOutliner.

(Which, incidentally gives access to the children of a row via two names – either as its children or its rows)

(FWIW OmniFocus has task (children = tasks) over most of its outline range, but makes life harder z – breaks recursive coherence– by calling some of these nodes, in the upper reaches of the tree projects or folders instead).

In OmniPlan each node is a task, and its collection of children are accessible by two alternative appellations – either as its tasks or as its child tasks)

Perhaps:

  • row
  • rows
  • flattened rows

Incidentally I think root is absolutely fine. Perfectly clear – the metaphor of a tree is widely familiar – and by now well supported by both Omni and Hogbay precedent.


For scripters using the omniOutliner omniJS interface, the familiar names are:

  • rootNode (there is also an isRootNode predicate)
  • parent
  • ancestors
  • children (there is also a hasChildren predicate)
  • descendants

Precedent solves many problems of common cognitive currency, as legal systems discovered millennia ago :slight_smile:


OmniOutliner: Items

Thanks for the help. I’ve just posted a preview of Bike with hopefully not too bad scripting support. If anyone here wants to try and give feedback I’m happy to give you a license key (ask me at jesse@hogbaysoftware.com).

This looks very interesting Jesse. You probably know this, but there is a long and storied history of Outliners in Mac Automation.

ScriptDebugger’s help documentation was written in an outliner. I wish more developers did that.

The first thing I noticed exploring Bike’s scriptability is that there is not a class for the contents of a document. That would make much more useful.

Also, looking at the app’s dictionary in Script Debugger’s explorer I’m seeing

selection range of window

with

anchor
  char
  line
anchor
  char
  line

But it seems that the values returned for these don’t actually match what’s selected in the UI.

I’m definitely going to play with this some.

Thanks for taking a look, but I think you are using an old version with just a private in progress script implementation… which reminds me that I forgot to update the website download! :slight_smile:

You should get the latest if you do Bike > Check for Updates. I’m sorry about the confusion.

I don’t know much first hand pre macOS X. I do know OmniOutliner has pretty good scripting support. Anyway would love to here what your old time favorite’s are/were.

In the latest version Bike (44) it should be that document has both rows and child rows collections. Is that what you are after?

Yeah, sorry that was an idea I never fully implemented and removed for now with the goal of simplification.

This is all very interesting, I’ll take a look.

The first released Inter-application scripting system for Mac (pre OSX) was Frontier, from Dave Winer, which what based on and/or inspired by his outliner program.

There are probably others who could do a better job explaining it than I could.

Taking another look, with a few things in mind.

I’d like the dictionary language to make sense, or be well explained.

I’d like it to be easy to script

and, most importantly, I’m imagining how I would use this in practice and what scripting capabilities would I want.

I’m also thinking about additional features I’d like. (You know, more work for you!)

So the script below works off of the default text.

First, I have no idea what a hoisted row is, or the distinction between a child row and a row. (In a sense every row is a child row of something, and some rows have child rows).

With the script below it seems to treat every row and child row the same, until it comes to the row with the URL, then it seems to stop counting child rows.

Also, the property name is not really a name. What you have as name should be something like contents

name should be a user settable property of a row (or child row) that returns "" or mising value until the user sets it.

set the name of row x to "TV Channels"

I’d really like to see the selection options ironed out.
(I would suggest looking at BBEdit, or Script Debugger for dictionary inspiration).

I like the fact that the file format is html, but it doesn’t have a way to edit or show any text formatting.

Might also be nice to set the contents of a row to something other than text, like an object (a graphic, or a button, or a field).

I can see a few uses for this right out of the box, but I also see a huge potential.

tell application "Bike"
   tell document 1
      set rowText to {}
      repeat with x from 1 to count of rows
         set the end of rowText to name of row x
         if not (x > (count of child rows)) then set the end of rowText to name of row x
      end repeat
      
   end tell
end tell

Also, forgot to mention, it would be really nice if the folding system in Bike also worked in a browser. Dropping the doc on Safari, the folding icons turn into bullets or hollow bullets.

First things first: Remember that your Apple Event Object Model is a relationship-based graph, not an Object-Oriented DOM. If you try to think about it in OO terms, you will only hurt yourself. If you use OmniOutliner as a design reference then look at its AppleScript dictionary, not at its JavaScript API. Omni’s JS APIs use OO DOM design patterns so are not applicable here. (I can’t comment on the design or quality of OmniOutliner’s AS dictionary as I don’t use it myself, but ISTR Omni being better than average at implementing AppleScript support in general.)

Child rows will appear as row elements of the row class. Remember: element names are fixed, being the singular/plural form of the class name. Therefore you cannot model both the immediate children and all descendents as elements of row.

class: 
    row -- a single entry in an outline document
        elements: 
            row -- this row's immediate children
        properties:
            container : reference -- this row's parent row
            entire contents : reference -- all rows contained by this row

See Finder’s container class for reference. In terms of relationships, a row object has:

  • a one-to-many relationship with its child rows
  • a one-to-one relationship with its parent row†
  • a one-to-many relationship with all its descendents

BTW, I can’t tell you the [Cocoa app] design pattern for implementing the entire contents property in CocoaScripting because I don’t know it myself. (Frankly CS is a boat anchor.) Mark has spelunked the CS framework in the past, trying to push its naive behavior beyond its implicit assumptions and built-in limitations; perhaps he can advise.

All I can say is that is how the app’s AEOM should look and feel from the user’s POV. Actually implementing that behavior using CS, and getting it 100% correct, is mostly left to the poor developer.

It may be that CS forces you to implement the entire contents property as a list of single-object references (which CS-based apps often do), as opposed to a single multi-object reference. That UX sucks: client-side iteration forces the scripter to write a bunch of boilerplate repeat loops and drops script performance in a hole. While sometimes that’s unavoidable (e.g. setting the name property of each element to a unique string), when the same command is being applied to multiple objects it’s best for the application to handle the details.

For instance, let’s mock a delete command which says to the app “recursively delete all rows that don’t have any content”.

tell application "Bike"
    set theRow to …
    delete every row of entire contents of theRow whose note = "" and rows = {}
end tell

(Assume note is a property which contains the row’s user-supplied text.)

BTW, AppleScript syntax is weird and allows where clauses to appear out-of-line, so what that command is really saying is this:

    delete (rows where its note = "" and its rows = {}) of entire contents of theRow

Very powerful query, much like an SQL DELETE from <some container> WHERE <some test>.

Of course, an RDBMS only has to operate on a flat unordered set of records, whereas you may be implementing your AEOM over ordered NSArray<BKRow> collections, so ensuring your app performs each deletion in the appropriate order is left to you‡.

BTW, this is probably the point that you as developer decide to implement entire contents the lame dumb way (list of single-object references) purely for sake of getting your app on sale this side of Xmas, and let the scripter eat the full cost of using it. Implementing a second-rate AEOM is a lot quicker, cheaper, and easier than implementing a top-quality one, and most users will just be happy to get one at all. But that’s the tradeoffs you make as a businessperson who needs to sell product to justify the hours spent developing it.

† One caveat is how the root row object represents its parent. Assuming the root row object is represented as a property (i.e. one-to-one relationship) of a document object, then the root row’s container should be a one-to-one relationship to that document, whereas every child row’s container will be a one-to-one relationshipt to its parent row.

BTW, you might feel tempted to make the root row’s container missing value, which as a programmer would probably be your first instinct as that’s how your underlying OO Model would no doubt expose a BKRow instance’s container were you implementing a container property for ObjC/Swift use:

@property (readonly) BKRow * _Nonnull container;

var container: BKRow? { get }

But again look at your UI/UX from the user’s perspective and observe the principle of least surprise. In the GUI, which is what scripters will naturally use as their reference, all rows are contained by something; the only difference between a root row and every other row is the container’s type: a document instead of a row. The downside is you may have to write a bit more CS code to represent this more sophisticated abstraction to the user (since CS, despite being a ViewController framework, has some painfully tight coupling to the app’s Model); or you can punt it for sake of getting the product out the door and most customers will be psyched just that you bothered at all.

‡ CS-based apps are often good at effecting move/duplicate/delete commands in unsafe/incorrect order, causing bits of the user’s data to end up in the wrong position or even vanish entirely; a smorgasbord of off-by-one related errors. e.g. Try delete (every word where it contains "e") on a TextEdit document for hilarious results. So the poor script ends up having to do all the iteration itself anyway, just to work around the app’s and/or CS’s implementation bugs.

On reviewing the Bike dictionary:

  • row

    • rows elements is fine

    • get rid of child rows elements

    • id property looks okay (I’m assuming GUID string); it’s also good as it means users can get a stable reference to a row anywhere in the document [1]

    • link property should be named URL [2]

    • level looks fine

    • the name property I’m not sure about; there are probably arguments both for and against, pertaining to how outliners are used by their users. I suspect leaf nodes typically contain arbitrary text of arbitrary length while parent nodes typically contain ad-hoc hierarchical headings. However, unless a row in the GUI supports separate “heading” vs “body” fields, using name is probably the least worst choice as it supports row named “Some Ad-hoc Heading” reference forms.

  • document

    • you can either have row elements OR a root property, not both. Which is appropriate really depends on:

      1. How the app’s structure is presented in the GUI. If an open document has a single top-level row, which the user can expand or collapse, and only create child rows beneath it, then it makes sense to present that row as a root property. Or, if the GUI never reveals this “special” node to the user and presents its children as zero or more rows visible in the document window, then present those rows as elements of the document.

      2. The nature of the root node. Does it have special characteristics that distinguish it from all other nodes (e.g. is it text-less, and exists in the Model only to serve as container)? Or is it an ordinary node exposing the exact same user-visible attributes as every other node?

    • again, get rid of child rows elements

    • id fine

    • root row – If you are exposing the root row object as a document property, I would just call that property root. That it contains a row object is already apparent from its type; and you don’t have a type named root so there’s no need to wrangle the property name to disambiguate the two. Two other property names worth considering: top row, outline. The word “top” better reflects the visual structure and may well map better to a typical user’s mental model of an outline. (“root” is a very programmer word, so only appropriate if this app is aimed specifically at techies. For everyone else in the world, it means “at the bottom of a tree.”) Or, from a branding POV if nothing else, I would consider outline, which is both descriptive and suggests this is the most important property in there.

    • hoisted row – I have no idea what this means (hint: even if the term “hoisted” is familiar to existing outliner users, the description text you’ve provided is entirely useless)

    • focused row – I am unclear how this differs from selection

    • selected text, selection row, selection rows – this is obviously a mess, but I’m assuming you’ve just thrown them all in for purposes of comparison and will eventually whittle down.

  • application

    • font size, background color, foreground color – The properties look fine, but the nature of their descriptions suggests they could/should be amalgamated into a single, self-explanatory preferences object class which is exposed as a preferences property of application. Whether you think that worth doing depends on whether you expect to add more application and/or preference properties in future.

      Also, if in future you might make each window’s visual appearance individually customizable, factoring these attributes out into a discrete object now makes sense. One might then name the document’s property appearance, the application’s property default appearance, and the record type or class appearance options.

  • collapse/expand – get rid of these commands and add a single boolean property to the row class, named either collapsed or expanded depending on which is the more intuitive. e.g. To shallow expand row elements:

    set expanded of every row of someRow to true
    

    To collapse an entire subtree:

    set expanded of [every row of] entire contents of someRow to false
    
  • select – I know it’s pretty common to see this command in apps that support scripting selections, but again if users can set a selection via a document’s selection (read-write) property then it’s really redundant. Also, if you do keep select the row(s) to select should be passed as the direct parameter; there should not be an at parameter. (The document is already specified by the row reference.)

    One reason you might want to provide a discrete selection command: select myBikeURL. While the same operation may also be expressed as set selection to every row whose URL = myBikeURL, the explicit command form is much shorter and more convenient.

  • import – Get rid of this. The idiomatic operation is to open both documents in the app, then e.g.:

    duplicate rows of document "Foo" to beginning of rows of document "Bar"
    

    The only reason you might want a discrete import command is for opening a foreign-format file and for some reason can’t express the transcoding options as an optional with options parameter to a standard open command. (Again, AS and CS might make extending standard commands with custom parameters excessively painful, necessitating a fallback to some awkward UX kludge just for sake of making it work at all.) For example, when reading an outline from a web URL or other non-filesystem-based resource.

    (Alas, macOS and AppleScript really show their age when it comes to reading and writing file-like objects from anything other than a traditional mounted disk. There’s no reason in principle that open and save commands couldn’t accept non-file URLs, but good luck explaining to both that it isn’t the 1980s any more.)

  • export – Again, I’m inclined to say get rid of it: a standard save command can express the file format. The only caveat is if users will frequently export fragments of a document, in which case export ROW[S] to FILE as FORMAT is obviously more convenient than copying the desired rows to a new, empty document and saving that.

Oh, and there’s no reason to call the file format enumeration saveable file format. Just file format is fine. Also consider if the enumerators really need the Format suffix. (It’s probably best to keep it, just to avoid any keyword conflicts with type names, although the word “format” should probably be lowercased.)

Your open/save/etc commands should also probably be able to infer most if not all documents’ file type just from existing file name extensions (e.g. foo.opml), so if there is no explicit as property then use the file name extension if possible and only fall back to a default type if neither is available. When saving a file with neither explicit as parameter not file type extension, the default should probably be native Bike format to avoid any possible degredation of the user’s data. When opening an unknown file, you could always try sniffing the first few bytes to see if it looks like a native Bike or OPML XML, and treat as plain text if it’s obviously neither.

[1]

[2] Were CS and AS better designed and used coercions properly, this property wouldn’t be needed as one would just say get row X of row Y of document Z as URL and the get command would return a URL string instead of an object reference. But they aren’t and they don’t, so an explicit URL property is the best option.

To be clear … rows is same idea as entire contents idea, just implemented as a element instead of as a reference. On the other hand child rows is an element of directly contained rows.

To me they are still both necessary because:

  1. I need rows for nice efficient implementation. In particular they allow:
  • Me to return global row references that are fast to create, resolve, and are stable as rows move about the outline:

    row id "3C" of document "Untitled"
    
  • Users to quickly and efficiently find rows anywhere in the outline by id such as:

    row id "todos" of document "Untitled"
    
  1. I need child rows because otherwise I find it confusing when trying to manipulate the outline. For example if I only have the rows element and I have this outline:

     hello
         one
             a
         two
         three
    

    Now let’s say I want to make a new child of “hello” after “one”. If I have the child rows collection then it’s pretty easy I can do something like:

    tell "hello" to make child row at after "one"
    

    On the other hand if I only have the rows collection then I’m not sure what to do. I might try this:

    tell "hello" to make row at after "one"
    

    Maybe that would work, but I think it would instead insert the new row after “one”, but before “a” which is not that I want. Also it’s unclear to me what level it would be inserted at. I could resolve these by creating the row and also passing in a value for level, but figuring out the proper level is difficult I think.

If there’s a clear way to specify all outline locations using just the rows collection then I would be happy to drop child rows, but I don’t see that yet.

What if I keep my current design, but mark child row as hidden. I’ve just tried that and things still seem to work.

That would allow me to keep these two element collections, but the child row class no longer shows up and confuses in the dictionary class listing.