Honey, I Shrank the Partition!

Luis de la Rosa asked in a comment to this post about my experience resizing partitions.

Before Monday, my experience was nil. The Mac traditionally hasn’t had utilities that can resize partitions, and OS X especially has had none.

That’s changed. Now, there’s VolumeWorks ($59.95) from SubRosaSoft and iPartition ($44.95) from Coriolis Systems.

iPartition is cheaper: is it better? VersionTracker says so, 4 stars versus 3 stars. I decided to buy it and try it.

First of all, it doesn’t come with a bootable CD. The Web site directs you to BootCD by CharlesSoft, but that is not compatible with Mac OS X 10.4.

I got around this problem by attaching my laptop to my tower via a firewire cable and booting the laptop into target mode, so the partitions showed up in the Finder on the tower. iPartition can resize the partitions of a drive connected via FireWire just fine.

First, I copied the entire contents of all the laptop’s partitions to my tower. I used cp from the command line: on 10.4, cp will also copy resource forks.

I also used Apple’s Disk Utility application to make sure all the laptop partitions were OK.

I used the bundled program iDefrag Lite to compact the used bytes of my data partition, then used iPartition to shave 15 Gb. off of it and distribute them to the three system partitions. The UI for this in iPartition is especially nice: you drag the sides of brightly-colored pie wedges representing the partitions on disk to make them bigger or smaller. A selected “slice” is half-removed from the pie, so it’s easier to see and manipulate.

All the steps together took maybe 8-9 hours.

I rebooted the laptop, and haven’t had any problems yet.

svnX: A Review

svnX looks like it’s the most full-featured GUI Subversion client on the Mac. Nate Silva asked me about it way back in this comment in my first Subversion post. So, how is it?

It’s got a good deal of the functionality I want.

• I can see the state of the files in my working copy: modified, added, removed.

• It supports visual diffs, and for extra points, it supports all the major external apps that provide this.

• For a repository, it can show me all the files affected by a particular check-in. It supports deleting, moving, and copying files in the repository.

All in all, it’s a workable replacement for Xcode’s Subversion support. That’s how I use it, as an occasional replacement, to deal with the Subversion work I need done for files that aren’t in my Xcode project.

Now, before I get to what I don’t like about it, let me acknowledge two things.

One, svnX has been released under the GPL. I could dive into the code myself and fix the problems I’m going to describe. Unfortunately, I’m already in the middle of a project – that’s what I’m keeping in my Subversion repository, and why I need a Subversion client! That said, I may take this up at some point.

Two, I want to keep my criticism of the application separate from the application authors. I treat the failings of a project like this, done for free and presumably in one’s spare time, far differently than I would treat even a shareware application.

I said svnX had most of the functionality I need. But in the realm of polish, it is disappointing, shocking even.

Have a look at the screenshot of the repository window. Notice anything strange in the uppermost table? What’s that first column of radio buttons doing there? To do a diff between two check-in versions, I shit you not, you need to select one of them via the radio button column, and the other by a more normal selection highlight, and then click the FileMerge button.

Why is the UI set up in this bizarre manner? I don’t know for sure, but my suspicion is because it was easier. In Cocoa nibs, tables can be set to allow only single-row selection, or multiple-row selection. Multiple-row selection, as you can imagine, involves a lot more work to handle all the different possibilities. But it’s what users expect.

Actually, I simplified things a bit. Hitting the FileMerge button doesn’t go directly to a file merge. It actually brings up a sheet with exactly the same two tables that are at the top of the window the sheet is attached to, from which you have to select the versions you want to compare. What does that sheet refer to? It turns out, it refers to the file selected in the third table on the main window, though the sheet gives you no indication of that. You can’t multiple-select in that file table, either, so you can’t do several file comparisons at once.

svnX has separate windows for Subversion repositories versus Subversion working copies. You can see a screenshot of the working copies window here. You’ll notice this window is much more simple. The repository history is completely unavailable. All you can know from this window is whether any files have had changes made to them since the last check-in.

Again, I’m assuming the reason this was done was to make things easier. It would be much harder to create an intuitive user interface where the information about a working copy is overlaid on the repository itself…but that’s what I want.

I want to be able to compare my working copy file’s condition against what was checked in 10 revisions ago. I want to be able to see what check-ins were made on the files that I have edited, in the same table – a column you can hide, perhaps, so it’s not always cluttering up the window?

I want to be able to perform file and folder moves in the working repository before checking them in: right now, you can only do that in svnX in the repository window, where changes are immediate.

I want to be able to go from the row representing the file to the file in the Finder. Contextual menus seem totally unsupported.

There’s probably more, but this will do. Some of this stuff is small potatoes, but if I were to work on this, I’d want to tackle the big stuff first, which would probably mean rewriting a lot of the UI code. Not something to be undertaken lightly.

Before I go, I’m going to have to take issue with the name. “svnX”? You name a GUI application after Subversion’s crypticly-named command-line tool plus an “X” to show it’s on Mac OS X? Is that the best you can do?

Here are a few suggestions, marked as (taken) if I found an app with that name in http://VersionTracker.com:

“Subversion” sounds like “Subversive”, right? So how about: Saboteur (probably not), Instigator, Agitator, Provocateur (I like that one).

You’re gaining access to an otherwise unreachable destination, like a castle, right? So how about: Drawbridge, Bridge, even Moat.

It’s a record of changes, so how about: History (taken), Historian (taken), or Archiver. You use it to find or figure things out, so maybe: (Code) Detective, Codehound, maybe even Code Thief.

Finally, I’ll end on a positive note: I like the swirly blue icon.

Subversion Immersion: Changing How I Write My Home Projects

One of the things that sank my MacTADS project was that I let my perfectionist tendencies take over. It wasn’t enough to have things work: the files had to be formatted exactly the same way, had to use the exact same techniques everywhere, had to have exactly the right behavior.

I didn’t use an SCM system then.

Now that I’m using Subversion, I’m finding something pretty cool: the rewriting tendency is more easily resisted.

Instead of letting one thing lead me to another, and then to another (“OK, I’ve fixed that bug, but I’ve noticed this other inconsistency, I’ll just do a big find-and-replace to regularize that….”), I now have to stop and think, what kind of comment would I check that in with? Does it really make sense to lump together this two, or three, or five different changes? When I started out only wanting the first? All right, then, undo the beginnings of the other changes, check in what I have, and call it a night.

Now, if I didn’t care about the organization of my Subversion repository, this wouldn’t be a problem. I might not even use comments for my checkins! (There’s a circle of Hell reserved for such people, btw.) But I’m a perfectionist, remember?

Now, I haven’t yet turned this into greater productivity. I’m not getting things done faster. But I do think I’m avoiding getting things done even slower….

Any Sufficiently Advanced…

Others have commented on how the style of the iTunes 5.0 interface is different than any other Mac OS X 10.4 “Tiger” application. What’s more interesting to me is the contents of the new interface.

They finally added groups! I mean, folders. But they kept most of the wacky, colorful smart groups, I mean, sources, from 4.9:

iTunes 5.0 Xcode 2.1
Xcode iTunes iTunes Xcode

All of this leads me to rule #1 (No, not that rule #1!):

Any sufficiently advanced Mac OS X application is indistinguishable from Xcode.

Obj-C++ Apps and Xcode 2.1 Obj-C Unit Tests: Don’t Forget Step 9!

1. Open Xcode 2.1

2. Make a Cocoa application project. I actually have the OCUnit stuff installed from a previous Xcode version, so my list of new projects included “Cocoa Application + Test”, which I chose (but which you shouldn’t, see below). I called my project “Testy”.

3. Add a C++ class with a member function that’s defined in the .cpp file. I called my class “Testy” as well, and the member function “test()”.

4. In the build settings for the application target, turn off ZeroLink for all configurations: choose “All Configurations” as your configuration in the target’s Info window, select the ZeroLink setting, and hit the delete key. This should turn it from bold to non-bold, and the checkbox to unchecked.

5. Build and run the application, be sure it runs OK, then quit it.

6. Add a Unit Test Bundle target. Add your app target as a direct dependency. Set the target’s Bundle Loader and Test Host settings as specified in Chris Hanson’s wonderful Unit Testing Cocoa Applications post.

7. Add a new Objective-C test case class file. Be sure it has an .mm suffix. In that file, instantiate your C++ class, and call the member function from that instance.

8. Build the Unit Test Bundle target.

It should work, right? You’re using a default project, default targets, following the instructions carefully. But it doesn’t work. The unit test bundle can’t find the C++ symbols from the application.

And the reason why is an additional little wrinkle I discovered today.

9. In your application target’s Info window build settings pane, search on “symbols hidden”. Turn it off. Rebuild your application and your unit test bundle.

Now it works.

Step 9 isn’t needed for applications with Objective-C classes and Objective-C unit test bundles. But it is needed for applications with C++ classes and Objective-C unit test bundles.

So if you have that combination, as I do, don’t forget step 9!

P.S. Second lesson: if you’ve got the old OCUnit stuff on your system, get rid of it before using Xcode 2.1-style unit tests. They don’t go well together. Because I didn’t, I had a lot of “scratch head, fiddle with it some more” sub-steps in the above that you should be spared.

Subversion Incursion: Three Questions

Question #1
In a particular directory in my Subversion repository, I had made maybe ten subdirectories whose names were version numbers, missing their decimal points. Version 2.5.8 was directory name "258", 2.5.9 was "259", etc.

This stopped sorting correctly with version 2.5.10. D’oh!

So I wanted to change all the directory names to their full decimal point equivalents, and then commit the lot of ’em.

Trouble was, I mistyped one of my svn rename changes. I set one directory’s new location to be inside another directory’s new location. Quick, how could I undo that!?

I undid it by using the svn revert command on both the new location and the old location. But what about the case where one was inside another? I had a bad feeling about it, and I was right.

I wound up with Subversion thinking that one of the new locations was (a) an existing, checked-in directory, and (b) being moved to another branch (?). Like so:

    S  3.0.0

Multiple subsequent uses of svn revert were unable to change Subversion’s mind.

Now, the easiest way to undo all this is probably to nuke the working directory and check it out again.

But I’m never one for easy, so I decided to try mucking with the contents of the local .svn directories.

The .svn/entries file has a list of all the files, with changes. Each file is represented by an <entry/> tag. The schedule attribute seems to describe the rename directives I’ve been making.

But hand-editing this file didn’t change the result of a svn status call.

I didn’t want to screw around with it, so I nuked the directory and started over. This time I set up the svn rename calls ahead of time in a text file and double-checked them, so I would get them right the first time.

Has anyone had success hand-edited these entries files?

Question #2
Inside those version number directories mentioned above are code archives. I needed to update my code directories with files from within those the archives. Replace both files and files within subdirectories and files with subdirectories of subdirectories.

In fact, under normal circumstances, I would have just taken the directory that each archive expanded to and dragged that over to replace its code directory twin, because my code directories have the same layout as the archives.

But now that my files are in a Subversion repository, I had to worry about all those little hidden .svn subdirectories. If I just replaced a top-level directory in the repository with a directory from elsewhere via the Finder, all the .svn instances would vanish.

So: is there a utility that will do such a nested copy without actually replacing the directories themselves (and thus deleting their hidden contents)?

Question #3
Once the new files had been copied over, I needed to add them all to the repository. Oops! svn add requires you to specify the exact file (or maybe the location of the files?) to be added. But I’ve got a hundred of new files in a score of different directories.

Text files to the rescue again. I copy the results of an svn status call into BBEdit and sort the lines, so all the lines that start with ? come first, then manipulate the text a little more to get a list of svn add commands.

Is there an easier way?

Omnipreferential: Rolling My Own Prefs Window, Instead of Using Omni Frameworks

After a long hiatus, I am once again looking at the project that first got me using both the Omni frameworks and unit tests.

Why now? An application already exists that does what my project’s app does, but that existing app is Classic-only. Classic seemed to be moseying along just fine until splat! it hit the brick wall of Macintel. No more Classic.

I was using the Omni frameworks primarily for how they made your application’s preferences window easier to implement. The trouble is, the Omni framework projects are sorely outdated. The version you’re supposed to use don’t have native targets, which is a requirement for building Universal binaries.

So it’s time to give them the heave-ho. Does that mean I have to make my own preferences implementation from scratch?

One alternative is to use the Preference Panes framework from Apple. It’s primarily for System Preferences panes, but the documentation makes it clear that it can be used for application preferences, too. The trouble? If you use it for your own application, you have to manage the window and the toolbar yourself. Since that’s most of the work, and since, if you use the Preferences Pane framework, you need to package each pane as a separate bundle, which is even more work, I give it a pass.

Uli Kusterer has UKPrefsPanel and UKToolbarFactory available in the source code portion of his Web site. They look promising. The prefs panel even uses an NSTabView as an easy way to deal with the prefs panes, which is how I was thinking of doing it. But it doesn’t support automatic resizing of a preferences window to the height of each individual preference pane, which is something I definitely want.

In fact, I want my preferences window to look and behave exactly like the preferences windows of standard Apple applications like Safari and Address Book.

So I decide to work directly with NSToolbar, and see what sort of progress I can make.

Apple’s ADC Web site has ToolbarSample source code that’s a good starting point.

Following that sample, I make the NSToolbar myself in code, and add it to the window by sending the window the message setToolbar:. That API actually expands the window by the height of the toolbar: I don’t have to leave room for it myself in the window’s nib. The window also fully takes care of the user hiding and reshowing the toolbar, and saving the selected toolbar item, as long as I send it a setAutosavesConfiguration:YES message. Neat.

There are a lot of things that need some tweaking, though:

• I want to use an NSWindowController to encapsulate the preferences window nib, a straightforward Cocoa technique. But the controller doesn’t seem to use NSWindow’s frameAutosaveName value, which is settable in the nib. In fact, it seems to set it to nil. Instead, if I want to go that route (which I don’t, see below) I need to set the controller’s windowFrameAutosaveName value, which can only be done in code. Annoying.

• The preferences windows of Safari et al erase themselves and then use a cool drawer-like animation to resize between preference panes. I can get the latter using setFrame:display:YES animate:YES, but not the former. I handle this by adding an extra, empty NSTabViewItem after all the other meaningful ones. I switch to that, do the resize, then switch to the NSTabViewItem I really want afterwards.

• I want the individual preference pane’s size info to be in the nib, not something I have recompile to alter. But every NSTabViewItem within an NSTabView is the same size. I work around this by putting my own custom NSView inside each NSTabViewItem, and then putting all my widgets in that subview.

In my code, I check for that subview’s size, and resize the window to match it, when switching to that NSTabViewItem.

• I need to be careful with resizing: it order to get the the effect I want — which is that the window title bar stays stationary, and the bottom of the window slides up or down — in the normal Quartz coordinate system, I need to change both the window size and its origin, because the origin is in the lower left-hand corner of the window. So, every time I select a toolbar item, as far as Cocoa is concerned, I’m both resizing and moving the window. But NSWindowController doesn’t catch this, leading to erroneous positioning when I close and reopen the application.

• If I handle user defaults manually instead of relying on NSWindowController, I can save the window’s frame to the user defaults (a) when I choose a new toolbar item (see the sample for how to associate toolbar items with actions), (b) when the window is moved around by the user (via the notification NSWindowDidMoveNotification), and (c) when the window is resized (via the notification NSWindowDidResizeNotification). Now, the window is not user-resizable, but this notification is still invoked when the user shows or hides the toolbar, because that automatically changes the size of the window.

But why do that much work? Instead, I listen for the notification NSApplicationWillTerminateNotification, and save off the window frame then. Since user defaults are generally only saved to disk upon application termination, this doesn’t lead to a mismatch even if you force-quit the application, because neither the toolbar user default changes that have already happened, nor my code’s user default changes that haven’t happened yet, are saved.

Mail Enhancement

I like partitions. Got a bunch of ’em on my laptop: several smaller ones, and a large one for data. It’s an easy way to test different OS versions.

Easy, at least, until the OS started taking up so much damn space! Now, I regularly fill up the smaller partitions. One solution is to resize them, an issue I’ll get to later.

But another solution is to move the large personal data stores that are normally replicated on each OS partition to the data partition, where they belong.

For example: my email. Mail normally stores its data in ~/Library/Mail:

Apple Mail POP account advanced tab, showing Account Directory setting

Apple Mail IMAP account advanced tab, showing Account Directory setting

But I’m never prompted where I want to put this Account Directory, it always defaults to ~/Library/Mail, and as you can see, it’s dimmed, so I can’t change it in the user interface once the account has been created. Well, there may be a way, but it probably involves throwing away all the messages I already have, which I don’t want.

All the information on my accounts is stored in the plist file at ~/Library/Preferences/com.apple.mail.plist. Ah, that’s easy, I think to myself, I’ll just open that file in a text editor and I’ll….

Bzzzt! (Notice how I like that word?) The plist is in binary format: I wouldn’t recommend trying to modify it with TextEdit.

For this, I’ll need to open the plist file in Property List Editor, which is available, after an Xcode Tools install, at /Developer/Applications/Utilities/Property List Editor.app.

I use the "Dump" button in the upper right-hand corner of the Editor window to get a dump of the plist file as text, and then copy that text to TextEdit (or a better editor such as BBEdit) and save it off somewhere.

Now, I need to search on every instance of ~/Library/Mail/ in that file, and change that path to the new path I want, /Volumes/Data/Mail/. The primary location for this in the plist is: MailAccounts → <account dictionary> → AccountPath.

What’s surprising is that this isn’t the only location where I find that path. In fact, it seems that the full path in the file system to the account directory is what’s used to refer to that account throughout the plist. I make sure to change them all.

The plist also contains the path ~/Library/Mail Downloads. I change that to /Volumes/Data/Mail/Downloads, just to keep all my Mail data in the same Mail folder.

Now that I’m done with the find-and-replace steps, I close Mail and back up all its data. Very important!

Next, I go to the ~/Library/Mail folder. I find the folder called Mailboxes, if it exists, and copy it to the new location.

I find every folder of the form ACCOUNTTYPE-username@account.domain.com, where ACCOUNTTYPE is IMAP or POP, etc., and account.domain.com is your incoming mail server, and copy them over as well. I don’t copy everything in ~/Library/Mail.

I copy the ~/Library/Mail Downloads folder into the new Mail folder, and rename it just Downloads, to match what I specified in the new plist, above.

Finally, while Mail still is closed, I replace the old binary plist (which I also backed up) with my newly edited, text plist. I say a little prayer, and reopen Mail.

It works!

Apple Mail changed Account Directory setting

Finally, once I’m sure everything is working, I get rid of the Mail folders on my OS partition. I follow all the same steps as listed above for my other OS partitions (except for populating /Volumes/Data/Mail, which I do only once with the most up-to-date data), and I’m done.

Feedback #1 If all I have is IMAP accounts, why not just go ahead and never store anything locally? Problem solved!

For one, IMAP is slow enough as it is: if I have to download all my messages from the server every time I open Mail, it’ll go even slower! For two, Mail tells me it won’t be able to check for spam under that configuration, though I haven’t verified this.

Feedback #2 If you do steps X, Y, and Z, you can get that widget to activate, and move the storage. Why not just do that?

As I said above, there may be a way to change the Account Directory value in the UI, but I suspect it only lets you do that if you empty your local storage, which I don’t want to do. If you do find some other series of steps that don’t have that drawback, let me know!

Also, remember that Mailboxes folder I moved? That holds all the local mailboxes I made, via Mailbox→New Mailbox…. I can’t move that to my central location without delving into the Mail plist as I’ve described above.

Update: There are a couple of good comments that describe both simpler and yet more all-or-nothing solutions than the one I proposed. Have a look!

Also, I originally advised moving the folder Icons, but it’s both not where I said it was, and you shouldn’t move it. It’s not at ~/Library/Mail/, it’s just at ~/Library/, and that’s a good thing, because the icons are used by more than one application. So leave ’em there!

Authority Issues II: The Dialog That Wouldn’t Die

Last time around, I talked about the Security framework. It is a set of “beneath the hood” C-based APIs you can use from Carbon or Cocoa.

In addition, there’s the Security Interface framework, which is a Cocoa framework of standard security UI widgets.

One of the widgets in this framework is the SFAuthorizationView, which looks like so in its unauthorized and authorized states:
SFAuthorizationView, locked iconSFAuthorizationView, unlocked icon

You tell it which right you want it to use, and it manages an AuthorizationRef for that right. It’s a very useful widget, but it has a gotcha.

You add an SFAuthorizationView to a window in your nib file by doing the following:

1) Drag the SecurityInterface/SFAuthorizationView.h header to your nib.

2) Make a custom view.

3) Change that custom view’s custom class to SFAuthorizationView.

Here’s some code for a delegate class for such a window. It assumes the delegate has an SFAuthorizationView outlet called “authorizationView”:

- (void)awakeFromNib
   [authorizationView setString:"right.to.party"];
   [authorizationView updateStatus:self];

   [authorizationView setFlags:
      kAuthorizationFlagInteractionAllowed |

   [authorizationView setAutoupdate:YES];

The first two messages are necessary to set up the authorization view. Without a specified right, the view can’t function. Without the updateStatus: message, it won’t show the lock icon correctly.

You’ll notice that the value for the setFlags: message is the same value I used for the second AuthoriaztionCreate)() function call in my last post. It serves the same purpose here that it did there.

But it’s the last call that’s the gotcha. You send the message setAutoupdate:YES if you want the authorization view to automatically change its appearance and behavior when, for example, an authorization expires, which they normally do after 5 minutes. So far so good.

But if that is combined with the flag kAuthorizationFlagInteractionAllowed, and if a user decides to cancel out of an authorization dialog instead of typing in a valid password…the then dialog comes back. Cancel it again, it comes back again. If you don’t have the password — say, for example, you’re not an adminstrative user for the Mac in question — the only way to get the dialog-that-won’t-die to go away is to force-quit the entire application.

Not good. There are two workarounds for this gotcha:

1) Don’t use kAuthorizationFlagInteractionAllowed. This means that when an update occurs, for example an authorization times out, the user won’t be prompted to authorize again immediately. Instead, the lock icon will just go ahead and lock itself. There isn’t a “nyah nyah” sound, but there might as well be.

2) Don’t set your view to autoupdate. You might do this if you’re using a right that, say, never expires.

For anyone using or thinking of using the SFAuthorizationView widget, I hope this helps.

Authority Issues: Gotchas with the Security Framework

You see those pesky authorization dialogs every so often, right? "Finder requires that you type your password." "BBEdit requires that you type your password." This means the application involved is using the Security framework to perform an action with, say, administrative privileges that it wouldn’t normally have.

If you turn down the Details disclosure triangle in such a dialog, you see the name of the app and an arcane Requested Right string.

These rights should be added to what’s called the "policy database," stored in the file /etc/authorization, when the application that wants to use them is first installed. For a full explanation, see Performing Privileged Operations With Authorization Services, but for this post, I’m interested in the API you use to add those rights.

Here’s the example given in the AuthorizationDB.h header in the Security framework.

OSStatus status =
      CFSTR("Authorize sending of a fax"),

Looks reasonable, right?

Bzzzt. Sorry! If you actually try to run this code, at least on Mac OS X 10.4 "Tiger," you get a −2147418108 error. What’s a −2147418108 error? No clue. But it’s not good.

The first parameter is of type AuthorizationRef. You get a valid authorization reference if the user types her password into one of those dialogs, but you don’t need special permission to add a new right to the policy database. Hence the null value in the example. But it doesn’t work.

Here’s what you need to do instead:

AuthorizationRef authorization = NULL;

OSStatus result =
      NULL, NULL,

if (result == noErr)
   result =
         CFSTR("Authorize sending of a fax"),


AuthorizationCreate() actually has a whole bunch of options you can use, but here I’m using it with minimal values to create a blank, good-for-nothing AuthorizationRef. Even so, that’s good enough for AuthorizationRightSet(). With this code, there’s no −2147418108 error.

So we’re done? Not yet!

You’ll notice above that I changed the third, "rule" parameter of AuthorizationRightSet() from the kAuthorizationRuleIsAdmin in the original sample to kAuthorizationRuleAuthenticateAsAdmin. With that rule, you are shown an authorization dialog when I use AuthorizationCreate() like so:

AuthorizationItem myItems =
   { "com.ifoo.ifax.send", 0, NULL, 0 };

AuthorizationRights myRights =
   { 0, &myItems };

AuthorizationFlags myFlags =
   kAuthorizationFlagInteractionAllowed |

AuthorizationRef authorization = NULL;

OSStatus result =
   AuthorizationCreate(&myRights, kAuthorizationEmptyEnvironment,
      myFlags, &authorization);

This call’s a lot more complicated, eh? But it boils down to trying to get a valid authorization for the right we added.

Here’s the dialog you see in response:

Authorization dialog with erroneous caption

Oops! If you fill in the fourth, "description key" parameter of AuthorizationRightSet() with a string as shown in the header example, that "description key" is concatenated with the default system prompt in any authorization dialog that refers to that right.

Passing NULL for the "description key" parameter uses just the default system prompt, which works much better.

Now we’re done.