How about this ... ST1 patch that should accept strings and numbers as model ids ... whilst we wait for bug to be patched in ST2 ....

WARNING I've only done basic testing on this so far .. use at your own risk!
I suspect there are other bugs that remain around the setting of the phantom flag ....

Includes additional check when web storage quota is exceeded ....

Code:
Ext.override(Ext.data.WebStorageProxy, {    /**
     * @private
     * Saves the array of ids representing the set of all records in the Proxy
     * @param {Array} ids The ids to set
     * 
     * PATCHED TO CHECK FOR STORAGE QUOTA OVERFLOW 
     */
    setIds : function(ids) {
        
        var obj = this.getStorageObject(), 
            str = ids.join(",");


        obj.removeItem(this.id);


        if(!Ext.isEmpty(str)) {
            try {                                                        // + PATCH 
                obj.setItem(this.id, str);
            } catch(e) {                                                // + PATCH 
                if((e.name).toUpperCase() == 'QUOTA_EXCEEDED_ERR') {    // + PATCH 
                    // do something here (fire error event, etc.)
                    // only when in the desktop ... iPad will prompt you 
                    Ext.Msg.alert('WebStorageProxy', 'You have exceeded your local storage quota');
                }                                                        // + PATCH 
                // else throw event to caller                            // + PATCH 
            }                                                            // + PATCH 
        }
    },
    
    // PATCHED TO CHECK FOR DUPLICATE IDS 
    create: function(operation, callback, scope) {
        var records = operation.records,
            length  = records.length,
            ids     = this.getIds(),
            id, record, i;
        
        operation.setStarted();


        for (i = 0; i < length; i++) {
            record = records[i];


            if (record.phantom) {
                record.phantom = false;
                id = this.getNextId();
            } else {
                id = record.getId();
            }


            this.setRecord(record, id);
            
            // only add if not already in the key    // + PATCH 
            if (ids.indexOf(id) < 0){                // + PATCH 
                ids.push(id);
            }                                        // + PATCH 
            
        }


        this.setIds(ids);


        operation.setCompleted();
        operation.setSuccessful();


        if (typeof callback == 'function') {
            callback.call(scope || this, operation);
        }
    },
    
    
    
    /**
     * @private
     * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data
     * @param {String} id The record's unique ID
     * @return {Ext.data.Model} The model instance
     * 
     * PATCHED TO ALLOW FOR STRING OR INTEGER MODEL ID 
     */
    getRecord: function(id) {
        if (this.cache[id] == undefined) {
            var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
                data    = {},
                Model   = this.model,
                fields  = Model.prototype.fields.items,
                length  = fields.length,
                i, field, name, record;


            for (i = 0; i < length; i++) {
                field = fields[i];
                name  = field.name;


                if (typeof field.decode == 'function') {
                    data[name] = field.decode(rawData[name]);
                } else {
                    if (field.type.type == 'date') {              // +patch
                        data[name] = new Date(rawData[name]);      // +patch
                    } else {                                      // +patch
                        data[name] = rawData[name];
                    }                                              // +patch
                }
            }


            record = new Model(data, id);
            // @TODO: make sure we don't have to set phantom to false. we give it an id, so it shouldn't
            // be phantom after we created it right?
            // record.phantom = false; 


            this.cache[id] = record;
        }
        
        return this.cache[id];
    },


    /**
     * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data
     * @param {Ext.data.Model} record The model instance
     * @param {String} id The id to save the record under (defaults to the value of the record's getId() function)
     * 
     * PATCHED TO ALLOW FOR STRING OR INTEGER MODEL ID 
     */
    setRecord: function(record, id) {
        if (id) {
            record.setId(id);
        } else {
            id = record.getId();
        }


        var rawData = record.data,
            data    = {},
            model   = this.model,
            fields  = model.prototype.fields.items,
            length  = fields.length,
            i = 0,
            field, name, obj, key;


        for (i = 0; i < length; i++) {
            field = fields[i];
            name  = field.name;


            if (typeof field.encode == 'function') {
                data[name] = field.encode(rawData[name], record);
            } else {
                if (field.type.type == 'date' && Ext.isDate(rawData[name])) {     // + PATCH 
                    data[name] = rawData[name].getTime();                        // + PATCH 
                } else {                                                        // + PATCH 
                    data[name] = rawData[name];
                }                                                                // + PATCH 
            }
        }


        obj = this.getStorageObject();
            key = this.getRecordKey(id);
        
        //keep the cache up to date
        this.cache[id] = record;
        
        //iPad bug requires that we remove the item before setting it
        obj.removeItem(key);
        obj.setItem(key, Ext.encode(data));
    },




    /**
     * @private
     * Returns the array of record IDs stored in this Proxy
     * @return {Array} The record IDs. Each is cast as a Number
     * 
     *  PATCHED TO ALLOW FOR STRING OR INTEGER MODEL ID 
     */
    getIds: function() {
        var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
            length = ids.length,
            i,
            idType = this.getModelIdType();                    // + PATCH 


        
        if (length == 1 && ids[0] === "") {
            ids = [];
        } else {
            // cast to numbers if model id is a number too
            if (idType === 'number'){                        // + PATCH 
                for (i = 0; i < length; i++) {                // + PATCH 
                    ids[i] = parseInt(ids[i], 10);   
                }                                            // + PATCH 
            }
        }


        return ids;
    },
    
    /*
     * NEW METHOD FOR PATCH TO ALLOW FOR STRING OR INTEGER MODEL ID 
     * get data type of model
     * @return {String} data type of the model id 
     */
    getModelIdType: function(){
        
        // has this already been checked?
        // assumes that models won't change their key type dynamically!
        if (this.modelIdType){
            return this.modelIdType;
        }
        
        var model = this.model.prototype,
            idProperty = model.idProperty
            idField = model.fields.get(idProperty);


        this.modelIdType = 'number';
            
        // if idProperty not found in field list -> will be a generated integer    
        if (idField){
            this.modelIdType = idField.type.type;
        }
        // handle other configuration values 
        if (this.modelIdType === 'int'){
            this.modelIdType = 'number';
        }
        
        return this.modelIdType;
        
    },


    
    //inherit
    //  PATCHED TO ALLOW FOR STRING OR INTEGER MODEL ID 
    destroy: function(operation, callback, scope) {
        var records = operation.records,
            length  = records.length,
            ids     = this.getIds(),


            //newIds is a copy of ids, from which we remove the destroyed records
            newIds  = [].concat(ids),
            i,
            // to ensure correct casting 
            idType = this.getModelIdType();


        for( i = 0; i < length; i++) {


            // fix to ensure both numeric and string entries are matched -> removed 
            if( idType === 'number' ) {                    // + PATCH 
                newIds.remove(records[i].getId());        // 
            } else {                                    // + PATCH 
                newIds.remove(records[i].getId() + ''); // + PATCH 
            }                                            // + PATCH 


            this.removeRecord(records[i], false);
        }


        this.setIds(newIds);


        if( typeof callback == 'function') {
            callback.call(scope || this, operation);
        }


    }




});